NightPxy 个人技术博客

java-ThreadLocal

Posted on By NightPxy

概述

ThreadLocal是一种用资源隔离的形式(为每个线程分配副本)解决的线程安全

源码

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

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

ThreadLocal最核心的主题就是WeakReference<ThreadLocal<?>>.也就是说ThreadLocal的本质是一个以线程对象为键,值中再包含一个Hash的弱引用哈希表

ThreadLocal内存泄露

有很多描述都说ThreadLocal会引起内存泄露,理由如下
从源码可以看出ThreadLocal的本质是以线程对象的弱引用为键的大Hash.理由就是这个弱引用.弱引用如果被GC后,就无法从以这个弱引用为键从ThreadLocal读取了.但是实际其中的值又继续存在其中,并且因为这个引用关系也无法回收.这就构成内存泄露了

这个说法对,也不对.现在来详细分析一下这里

首先,焦点问题在这个弱引用.那么换成强引用行不行?
很可惜答案是不行.因为ThreadLocal的本体是一个static class Entry,如果是强引用,那么当线程对象本身会因为ThreadLocal始终维持了一个引用而无法回收.这本身就会造成内存泄露
使用弱引用的目的就在这里,JDK期望当线程消亡时伴随Thread线程对象的消失时,ThreadLocal中也能自动被GC

其次,线程对象因为弱引用被GC后,ThreadLocal中的值会不会有问题
这里的确是有点问题,JDK对此采取了一个补救措施,就是在每次get的时候都会从删除null键.(null是因为弱引用回收了)

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

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
     Entry[] tab = table;
     int len = tab.length;

     while (e != null) {
         ThreadLocal<?> k = e.get();
         if (k == key)
             return e;
         if (k == null)
             expungeStaleEntry(i);
         else
             i = nextIndex(i, len);
         e = tab[i];
     }
     return null;
 }
  • 首先从ThreadLocal的直接索引位置(通过ThreadLocal.threadLocalHashCode & (len-1)运算得到)获取Entry e,如果e不为null并且key相同则返回e
  • 如果e为null或者key不一致则向下一个位置查询,如果下一个位置的key和当前需要查询的key相等,则返回对应的Entry,否则,如果key值为null,则擦除该位置的Entry,否则继续向下一个位置查询
    这补救措施很弱是因为只有读取到null才会向下遍历清除null键

最好的解决办法是开发者自己来

  • ThreadLocal是提供remove方法的,最好的解决办法是.如果自己不再需要时手动remove掉