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  

首先,我们来看set

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

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

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

ThreadLocalMap实际上就是一个Map。但是注意到他没有实现Map的接口。
set对应的值key是当前的threadLocal对象。

get的代码非常简单。

   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();
    }

使用threadLocal后一定要记得remove

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

可以看到是调用了ThreadLocalMap的remove

ThreadLocalMap

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);
        }
remove
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

clear把entry对key的引用设置为null。让key被清除。remove将下图中【引用2】被清除。同时将map中key为null的value的【引用4】清除。

在这里插入图片描述

总结一下

可见整个设计的核心就是吧threadLocal对象作为ThreadLocalMap的key。实际上,threadlocal对象往往存在于单例或者静态条件下,也没办法将其设置为null,为多个线程共用。所以【引用3】会一直存在的。线程池只要线程不消失,【引用1】也不会消失。所以,调用remove只能清除弱引用2,同时清除弱引用2消失的对应的强引用4,能起到回收object的作用。

为什么引用2使用弱引用呢?这是因为在3强引用断掉后,必须保证ThreadLocal对象可以被回收。否则,那一滴定时永远也回收不了,

内存泄露问题

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,利于下次内存回收。

考虑到线程池场景下线程的长期存活,所以,一个提倡的事情是在使用完threadLocal后,手动调用remove。而不仅仅是将threadLocal的引用设置为空

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值