ThreadLocal

为什么使用

  1. 对线程不安全的类包装一层, 使之看起来是线程安全(实际上使用不是同一个实例), 典型的是SimpleDateFormat
  2. 线程私有的局部变量, 比如统计线程运行任务次数

线程局部变量如何实现

三叉戟 Thread, ThreadLocal, ThreadLocalMap

Thread对象中成员变量threadLocals(普通局部变量表), inheritableThreadLocals(可从父线程继承局部变量表, 线程初始化拷贝过来, 初始化之后不会同步)
ThreadLocalMap 以ThreadLocal为key, 值为value, 采用弱引用包装(gc)
引用关系为Thread --> ThreadLocalMap --> ThreadLocal

由于threadLocals是Thread的成员变量, 所以不同的线程拿到的ThreadLocalMap不同(ThreadLocal.getMap()), 拿到的ThreadLocal变量也就不同, 也就是实现了线程局部变量

内存泄露分析

先说结论, ThreadLocal内存泄露与弱引用无关
ThreadLocal内存泄露分为两个层面, 一是threadLocals 局部变量表, 二是ThreadLocal局部变量

我们假如线程一直不被回收(多见于不允许核心线程超时的线程池中的核心线程)

  1. 对于threadLocals泄露情况, 线程中有特别多的ThreadLocal实例, 造成threadLocals特别大, gc回收不了
  2. ThreadLocal本身泄露
    ThreadLocalMap中key为弱引用, gc每次都会回收key, 如果不调用ThreadLocal.remove 会造成value一直回收不了, 导致内存泄露(有一种说法是因为弱引用, 但是如果使用强引用, 不调用remove方法, 泄露只会更严重)
          #staleSlot 为当前remove的ThreadLocal在threadLocals中的索引
          private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            # 1. 将value置空, 去掉对value的强引用, 帮助值gc
            # 2. 将threadLocals中的slot置空, 帮助gc
            // 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) {
                    # 顺带将已经gc回收了的ThreadLocal以及value删掉
                    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)
                            # rehash, 将之前由于冲突的Entry归位
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。