ThreadLocal源码分析

Posted z-qinfeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal源码分析相关的知识,希望对你有一定的参考价值。

1.测试代码 

public class ThreadLocalTest {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        threadLocal.set("郑钦锋");
    }
}

 

   public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程关联的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            # set值, this: 指ThreadLocal
            map.set(this, value);
        else
            createMap(t, value);
    }

 

技术图片

 

 

下面分析set(ThreadLocal tl,Object value)方法

  private void set(ThreadLocal<?> key, Object value) {

    # 底层使用Entry数组存储数据
    Entry[] tab = table;
    # 默认16
    int len = tab.length;
    # 计算槽位
    int i = key.threadLocalHashCode & (len-1);
    # 从数组中获取key对应槽位的值,一个Entry对象
    for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
        # Entry对象的key就是ThreadLocal对象
        ThreadLocal<?> k = e.get();
        # 如果从槽位中获取的Entry的k 等于 传入的key,将新值赋到这个Entry上面,然后结束set方法
        if (k == key) {
            e.value = value;
            return;
        }
        # 如果槽中Entry的key为空,最终会调用expungeStaleEntry方法移除这个槽中的数据
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    # 回到正常逻辑
    # 如果槽位i是空,创建一个Entry对象,并将这个Entry对象放到i这个槽位上
    tab[i] = new Entry(key, value);
    int sz = ++size;
    # 底层调用expungeStaleEntry方法移除槽中key(ThreadLocal)为null对应的Value值,同时清空这个槽位
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        # 扩容
        rehash();
}

 

看了上面代码注释,我们有必要分析以下两点:

(1) 创建Entry对象的具体的过程

(2) expungeStaleEntry方法是如何清空key为null的Entry对象的

 

接下来先看第1点,如何创建Entry?

    static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

代码其实很简单,就是将key交给顶级父类Reference处理,一直跟踪代码会看到下面这行代码 

    private T referent;         /* Treated specially by GC */  真惨

 

接着看expungeStaleEntry方法,这也很简单,结合着cleanSomeSlots方法来看,直接上代码 

 

     private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                # 如果key为null,删除
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }    
 private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

 

ok!

最后总结一下:

别人写的代码就是复杂,对象是包了一层又一层,ThreadLocal就是下面这模样

技术图片

 

(1) 大家可以看到,Entry的key是一个弱引对象,这样的对象在GC有可能被回收掉,但是Entry的Value值(就是我们set的值)呢,它在一条引用链上,如果本线程没有被干掉,它是不会被回收掉的。而且因为key又是null, 永远都不会被使用了,一个僵尸对象! 这样导致内存泄漏也就不难理解了

(2)这么明显的漏洞,ThreadLocal肯定也注意到了,所以它在set,get,remove方法时,都会对key为null的Entry对象进行清理

  (3)   内存泄漏的根本原因是: Thread---> ThreadLocalMap---> Entry在一条引用链上,Thread不死,后面的也死不了!

 

记录: FastThreadLocal

听说很牛逼,效率很高,结果去官网上一看,它果然很无耻写到:

这是一个ThreadLocal的变体, 使用它可以产生更高的访问性能, 它使用的是常量index,而不像ThreadLocal使用hashCode index去检索数组中的数据!

我觉得单是这一点,两者差别应该不大,时间复杂度都是圈1, 应该还有其它的原因,留着以后学习吧!

 

 

以上是关于ThreadLocal源码分析的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal源码分析

ThreadLocal源码分析理解弱引用和内存泄漏

ThreadLocal源码分析理解弱引用和内存泄漏

ThreadLocal 源码分析

ThreadLocal源码分析理解弱引用和内存泄漏

ThreadLocal源码分析_01 入门案例以及表层源码分析