• 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, 应该还有其它的原因,留着以后学习吧!

  • 相关阅读:
    WebSocket
    betterscroll
    css font
    luckycanvas
    ios系统使用占比
    设备趋势
    高效阅读代码工具
    逻辑地址、物理地址、线性地址、虚拟地址
    怎么用抓包工具Fiddler进行弱网测试【杭州多测师_王sir】【杭州多测师】
    后端测试,服务器端测试【杭州多测师】【杭州多测师_王sir】
  • 原文地址:https://www.cnblogs.com/z-qinfeng/p/11992828.html
Copyright © 2020-2023  润新知