浅谈ThreadLocal、ThreadLocalMap、Thread三者的关系

60 阅读2分钟

关系概览图

image.png

晒一下源码

Thread类

package java.lang;

public class Thread implements Runnable {

    // Thread类中维护了 ThreadLocalMap 变量,修饰符默认为 protected
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocal类、ThreadLocalMap类

package java.lang;

public class ThreadLocal<T> {

    // 静态内部类 ThreadLocalMap
    static class ThreadLocalMap {
    
        // 静态内部类 Entry,且其继承了弱引用,key值存入了弱引用对象中
        static class Entry extends WeakReference<ThreadLocal<?>> {
            // value 值,我们通过 ThreadLocal 存储的值的位置,就是这个 value 变量
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                // key 值直接赋值给了父类,其为弱引用
                super(k);
                // value 在本对象中,为强引用
                value = v;
            }
        }
        
        // Entry 数组,可以存储多个 value
        private Entry[] table;
        
        // ThreadLocalMap 构造方法
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // 构建一个Entry对象,存储数据,并放入table数组中
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

    }
    
    // 设置键、值
    public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取线程类内维护的 ThreadLocalMap 变量:threadLocals,第一次为 null
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 第一次为 null,创建 ThreadLocalMap
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        // 直接返回 Thread 类中 threadLocals 变量,第一次默认为null
        return t.threadLocals;
    }
    
    // 创建 ThreadLocalMap
    void createMap(Thread t, T firstValue) {
        // 调用构造方法创建 ThreadLocalMap,将构建的对象直接赋值给当前线程的 threadLocals 变量
        // 在上面的构造方法中,可知 键值对 会被构建为Entry对象,存入 Entry数组变量 table 中
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
}

通过上述代码可知(需结合代码注释理解):

  • Thread类和ThreadLocal类的包名相同,都是java.lang,而Thread中的threadLocals 变量修饰为protected,所以我们在业务代码中不能通过Thread直接去操作ThreadLocalMap,此处是故意设计为只能通过ThreadLocal去操作线程本地变量。
  • 跟踪上述代码中ThreadLocal类的set方法,可以知道:
    • 我们存入的key-value对,实际最终会被构建为Entry对象,并存入ThreadLocalMap类中维护的Entry数组:table变量中;
    • 通过ThreadLocal对象调用set、get方法操作线程本地变量时,其实是对当前线程中的变量进行操作,并基于此实现了线程之间的数据隔离;
    • 我们存入的key-value对,key直接赋值给了父类(弱引用)的变量,value在Entry对象中存储,顾其key为弱引用、value为强引用。

扩展

我们存入的key-value对,最终是以Map结构存储的吗?

ThreadLocal真的很容易导致内存泄露吗?怎么使用会导致其内存泄露

举一个ThreadLocal的场景应用

下期见…

推荐一本书,带你彻底搞懂JUC