美文网首页
threadLocal源码解析

threadLocal源码解析

作者: binecy | 来源:发表于2017-12-13 16:50 被阅读6次

    简单记录一些看threadLocal时遇到比较有意思的点。

    ThreadLocalMap

    ThreadLocal的数据, 存放在Thread的属性ThreadLocal.ThreadLocalMap threadLocals

    ThreadLocalMap可以看做一个简单的map<ThreadLocal, Object>, 他保存了每一个ThreadLocal和对应的值.
    如有两个ThreadLocal

    ThreadLocal<Integer> a = new ThreadLocal<>();
    ThreadLocal<Integer> b = new ThreadLocal<>();
    

    在t1线程中设置值:

    a.set(1);
    b.set(2);
    

    可以想象为t1.threadLocals[a] = 1, t1.threadLocals[b]=2

    对于不同的Thread, 每一个Thread中threadLocals分别保存了ThreadLocal和他们对应的值.
    如上面栗子中, 如果另一个线程t2也设置了

    a.set(3);
    b.set(4);
    

    那么可以想象为t2.threadLocals[a] = 1, t2.threadLocals[b]=2

    为什么不要直接在ThreadLocal中使用一个map存储对应的线程和值?
    如a.map[t1] = 1, a.map[t2] = 3, b.map[t1] = 2, b.map[t2] = 4
    我想, 应该是为了方便内存回收.
    使用Thread.threadLocals方式, 如果线程结束了, 那该线程set的值(如果没有其他引用)就可以被回收了.
    如果使用ThreadLocal.map模式, 已消亡Thread set的值会一直停留在内存中.(map.key会一直指向消亡的Thread)

    当线程退出时, Thread类会进行一些清理工作, 其中就包括ThreadLocalMap

    /**
     * 在线程推出前, 由系统回调,进行资源清理
     **/
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        target = null;
        // 加速ThreadLocalMap清理
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    } 
    

    WeakReference<ThreadLocal<?>>

    还有一个值得注意的点, ThreadLocalMap中的数据是存储在Entry[] table中,
    Entry的定义是

    static class Entry extends WeakReference<ThreadLocal<?>> {
        // 这个valu就是ThreadLocal.set的值
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    

    注意这里使用了WeakReference, 而不是Entry extends ThreadLocal<?>.
    WeakReference引用表明, 一旦没有指向 referent 的强引用, weak reference 在 GC 后会被自动回收.

    先看一个小栗子:

    ThreadLocal<Object> local = new ThreadLocal<Object>() {
        protected void finalize() throws Throwable {
            System.out.println(this.toString() + " threadLocal is gc");
        }
    };
    
    Object reference = new Object() {
        protected void finalize() throws Throwable {
            System.out.println(this.toString() + " object is gc");
        }
    };
    
    local.set(reference);
    
    // 去掉强引用
    local = null;   // 代码1
    System.gc();
    
    System.out.println(t);  // 代码2
    

    输出结果

    multi.ThreadLocalTest$1@1714d2a threadLocal is gc
    gc finish!!
    

    要通过debug查看Thread.threadLocals的情况.
    debug到代码1处, 可以看到

    1.png

    debug到代码2处, 可以看到

    2.png

    可以看到, gc后, entry的referent被回收了, 这时ThreadLocal可以被内存回收了, 如果使用
    Entry extends ThreadLocal<?>, 那么entry.key会一直引用ThreadLocal, 导致ThreadLocal无法被回收.

    值得注意的是,local虽然回收了, 但 reference对象 并没有回收, 哪怕你在gc前将其设为nullreference = null;, 因为threadLocals.entity中的value值依然引用它, 这点可能会造成内存泄露。想要及时回收它, 可以如下操作

    // 去掉强引用
    reference = null;
    local.set(null);
    local = null;
    System.gc();
    

    输出结果

    multi.ThreadLocalTest$2@1714d2a object is gc
    multi.ThreadLocalTest$1@1b6b5b4 threadLocal is gc
    gc finish!!
    

    那么除了内存回收, 使用WeakReference还有没有其他意义呢?
    我们可以看一下ThreadLocalMap的实现, ThreadLocalMap的数据存放在Entry[] table数组中, 通过hash算法实现一个类Map的数据结构,就来看一下ThreadLocalMap.set方法

    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        // 计算hash值
        int i = key.threadLocalHashCode & (len-1);
    
        // 找到可以set的位置
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
    
            if (k == key) {
                e.value = value;
                return;
            }
    
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
    
        // set 值
        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();   // 扩容
    }
    

    关键在查找可以set的位置上, 使用hash算法无可避免会遇到hash冲突, 常见的解决方法,
    如链路法, 在该位置使用一个链路存储多个冲突的值(HashMap的方法),
    这里使用的是线性补偿探测法, 发生冲突是, 使用当前位置继续进行hash计算(nextIndex(i, len)方法), 直到找到一个可以使用的位置.

    上面栗子中,

    • 如果entry为null, 可以直接创建entry放置到该位置.
    • 如果entry.referent(就是entry指向的ThreadLocal) == key, 可以直接替换掉value
    • 如果entry.referent==null, 这就是上面提到的, referent指向的ThreadLocal没有强引用了,所以referent被GC回收了, 既然这时ThreadLocal没有引用了, 所以这里就可以考虑替换该entry了, 不用再继续寻找合适的位置了.

    所以这里使用WeakReference, 还有一个意义就是减少hash冲突.

    看到这里, 不得不说ThreadLocal的实现, 还真是"有点意思"啊

    错误之处, 还望指出

    相关文章

      网友评论

          本文标题:threadLocal源码解析

          本文链接:https://www.haomeiwen.com/subject/tdkzixtx.html