ThreadLocal的实现原理(读书笔记)

ThreadLocal的set方法和get方法,从set方法开始:
public void set(T value) {
    Thread t = Thread.currentThread();//获取当前线程
    ThreadLocalMap map = getMap(t);//获取线程的局部变量
    if (map != null)//判断map是否存在
        map.set(this, value);//set值 key是当前ThreadLocal对象 value是value
    else
        createMap(t, value);//否则 创建一个map设置值
}
     get方法:
public T get() {
    Thread t = Thread.currentThread();//获取当前线程
    ThreadLocalMap map = getMap(t);//获取线程的局部变量map
    if (map != null) {//当map存在时
        ThreadLocalMap.Entry e = map.getEntry(this);//获取entry(键值对)
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;//返回值
        }
    }
    return setInitialValue();//返回null
}
     在了解了ThreadLocal的内部实现后,我看到一个问题,那就是这些变量是维护在Thread类内部的,这也意味着只要线程不退出,对象的引用将一直存在,
     当线程退出是,Thread类会进行一些清理工作,其中就包括清理ThreadLocalMap.下面是具体实现:在Thread类内部:
private void exit() {
    if (group != null) {
        group.threadTerminated(this);
        group = null;
    }
    /* Aggressively null out all reference fields: see bug 4006245 */
    target = null;
    /* Speed the release of some of these resources */
    threadLocals = null;
    inheritableThreadLocals = null;
    inheritedAccessControlContext = null;
    blocker = null;
    uncaughtExceptionHandler = null;
}
     因此,如果我们使用线程池,那就意味着当前线程未必会退出,(比如固定大小的线程池,线程总是存在的,) 如果这样,将一些大大的对象设置到ThreadLocal中,可能会使系统出现内存在泄漏,
     此时,你希望及时的GC,最好使用ThreadLocal.remove()方法将这个变量移除,就像我们习惯性的关闭数据库链接一样,如果你确定不需要这个对象了,那么就应该告诉虚拟机,把他回收掉,防止内存泄漏,
     另外一种有趣的情况是JDK也可能允许你想释放普通变量一样释放ThreadLocal.比如,我么你有时候为了加入GC.会特意写出类似obj=null之类的代码.如果这么做,obj所指向的对象就会更容易地垃圾回收器发现,从而加速回收,
     同理,如果对于ThreadLocal的变量,我们也手动将其设置null.比如t1=null.那么这个ThreadLocal对应的所有线程的局部变量都有可能被回收,我们写个小例子来看一看奥秘:
public class ThreadLocalDemo_Gc {
    static volatile ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected void finalize() throws Throwable { //重载了finalize() 当对象在GC时,打印信息
            System.out.println(this.toString() + " is gc");
        }
    };

    static volatile CountDownLatch cd = new CountDownLatch(10000);//倒计时

    public static class ParseDate implements Runnable {
        int i = 0;

        public ParseDate(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            try {
                if (t1.get() == null) {
                    t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
                        @Override
                        protected void finalize() throws Throwable {
                            System.out.println(this.toString() + " is gc");
                        }
                    });
                    System.out.println(Thread.currentThread().getId() + ":create SimpleDateFormat");
                }
                Date t = t1.get().parse("2016-12-19 19:29:" + i % 60);
            } catch (ParseException e) {
                e.printStackTrace();
            } finally {
                cd.countDown();//完成 计数器减1
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10000; i++) {
            es.execute(new ParseDate(i));
        }
        cd.await();//等待所有线程 完成准备
        System.out.println("mission complete!!");
        t1 = null;
        System.gc();
        System.out.println("first GC complete!!");
        t1 = new ThreadLocal<>();
        cd = new CountDownLatch(1000);
        for (int i = 0; i < 10000; i++) {
            es.execute(new ParseDate(i));
        }
        cd.await();
        Thread.sleep(1000);
        System.gc();
        System.out.println("second GC complete!!");
    }
}
     输出结果如下:
     在主函数Main中,先后进行了2次任务提交,每次10000个任务,在第一次任务提交后,我们t1设置为null 接着进行了一次gc,接着我们进行了第二次任务提交,完成后在进行一次gc,
     注意这些输出,.我们发现了当t1被设置为null时候,第一次gc 回收了.接着提交第二次任务,这次我们也是创建了10个线程,可以看到,虽然我们手动remove()这些对象,但是系统依然有可能回收他们.
     要了解这里的回收机制,我们需要进一步了解Thread.ThreadLocalMap的实现,之前我们说过,ThreadLocalMap是一个类似HashMap的东西,更精确地说,他更加类似WeakHashMap.
     ThreadLocalMap的实现使用了弱引用,弱引用是比强引用弱的多的引用,java在虚拟机回收时,如果发现若引用,就会立即回收,ThreadLocalMap内部由一系列Entry构成,每一个entry都是WeakReferenc<ThreadLocal>:
     
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
      这里的参数k就是map的key,v就是Map的value.其中k也就是ThreadLocal实例,作为弱引用使用(super(k)就是调用了WeakReferenc的构造函数,)因此,索然这里使用了ThreadLocal作为map的key,但是实际上,他并不是真的持有ThreadLocal的引用,而当THreadLocal的外部引用被回收时,ThreadLocalMap中的key就会变成null.当系统进行ThreadLocalMap清理时,就会自然将这些垃圾数据回收, 
posted @ 2016-12-22 18:10  Darcy_wang  阅读(267)  评论(0编辑  收藏  举报