ThreadLocal使用和原理

实现机制

1、每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。

    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //每个线程拥有一个map,取出来的是线程t的ThreadLocal.ThreadLocalMap对象

ThreadLocalMap map = getMap(t);
if (map != null) {
            //this->ThreadLocal实例是作为map的key来使用的
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        //初始化这个线程的ThreadLocalMap,并且返回null
        return setInitialValue();
    }    

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    protected T initialValue() {
        return null;
    }

3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //一个thread.ThreadLocalMap 可以维护多个不同ThreadLocal的值
            map.set(this, value);
        else
            createMap(t, value);
    }    

事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己线程的变量map中以ThreadLocal为key来取值,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。

内存泄露

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。

为什么用弱引用?

key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除,否则value内存溢出。

由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

使用举例

public class TestThreadLocal {

    private static int j = 0;

    public static void main(String[] args) {
        final ThreadLocal<String> tl1 = new ThreadLocal<String>();
        final ThreadLocal<String> tl2 = new ThreadLocal<String>();
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 30; i++) {
            es.execute(new Runnable() {
                @Override
                public void run() {
                    synchronized (TestThreadLocal.class) {
                        tl1.set("tl1"+"-"+j);
                        tl2.set("tl2"+"-"+j);
                        System.out.println("当前线程" + Thread.currentThread()+"-"+ j + "放入data=" + tl1.get());
                        System.out.println("当前线程" + Thread.currentThread()+"-"+ j + "放入data=" + tl2.get());
                        j++;
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("当前线程" + Thread.currentThread() + ":取出data=" + tl1.get());
                    System.out.println("当前线程" + Thread.currentThread() + ":取出data=" + tl2.get());
                }

            });
        }
    }
}

 

posted @ 2016-08-10 09:38  wade&luffy  阅读(272)  评论(0编辑  收藏  举报