ThreadLocal源代码分析

下面分析基于JDK1.8。首先来看一个简单的ThreadLocal使用的例子。

public class Test {  
      
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();  
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();  
  
    public void set() {  
        longLocal.set(Thread.currentThread().getId());  
        stringLocal.set(Thread.currentThread().getName());  
    }  
  
    public long getLong() {  
        return longLocal.get();  
    }  
  
    public String getString() {  
        return stringLocal.get();  
    }  
  
    public static void main(String[] args) throws InterruptedException {  
        final Test test = new Test();  
  
        test.set();  
        System.out.println(test.getLong());  
        System.out.println(test.getString());  
  
        Thread thread1 = new Thread() {  
            public void run() {  
                test.set();  
                System.out.println(test.getLong());  
                System.out.println(test.getString());  
            };  
        };  
        thread1.start();  
        thread1.join();  
  
        System.out.println(test.getLong());  
        System.out.println(test.getString());  
    }  
}  

上述代码输出:

1  
main  
11  
Thread-0  
1  
main  

get的代码非常简单。我们可以看到是维护了一个map。这个map的key是该threadlocal对象。你需要通过当前线程对象取到这个map。

   public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

这个map存储在当前线程对象中。

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	//thread类中的代码。
	ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap是定义在ThreadLocal中的静态内部类。这个Map没有使用Hashmap,而是该类作者自己实现的。首先是Entry类的实现,Entry类继承了WeakReference类。

    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
         //注意下面的类被生命为继承了WeakReference,但是实际上只有k调用了super,也只有entry->key是弱饮用。entry->value是强引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

这是因为这个map是保存在线程对象里的。如果不加处理,只要线程对象不消失,这些key和value就会永远存在下去!使用WeakReference,没有外部引用Entry内的key,该key(ThreadLocal对象)会被虚拟机回收。但是value并不会。清除value要等到resize操作中。扫描entry数组,发现key是null的,就直接把value也制成null,保证GC。

        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

ThreadLocal实现中没有使用锁。但是使用了AtomicInteger。这是为了服务于ThreadLocal该类的静态方法,nextHashCode。该类的初始nextHashCode被置为0,每一次调用都返回该值并加上0x61c88647。这个值用来标志该ThreadLocal对象的唯一序号。

	private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }   

为什么ThreadLocal要有唯一序号呢?因为ThreadLocal是要作为上面那个map的key。我们知道map的底层是数组实现的,将ThreadLocal的序列号与上容量,就可以快速定位数组中的entry对象了

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

内存泄露问题

ThreadLocal在ThreadLocalMap中是以一个弱引用身份被Entry中的Key引用的,因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收。这个时候就会出现Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap–>Entry–>Value,这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。
但是JVM团队已经考虑到这样的情况,并做了一些措施来保证ThreadLocal尽量不会内存泄漏:在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。


没有更多推荐了,返回首页