Java|从源码层面谈谈 ThreadLocal 线程私有实现方式

前言 ThreadLocal 是一个用于存取线程本地变量的类,通过其实例的 get/set 方法进行数据的存取,数据存取到 ThreadLocal 后,只有线程自身能访问到,如下图:
Java|从源码层面谈谈 ThreadLocal 线程私有实现方式
文章图片

线程私有示例代码 先看一段代码:

public static void main(String[] args) throws InterruptedException { ThreadLocal> threadLocal = new ThreadLocal<>(); Thread.currentThread().setName("主线程"); System.out.println("[" + System.currentTimeMillis() + "] " + Thread.currentThread().getName() + " 对 threadLocal 赋值"); threadLocal.set("Shawearn 你好, 我是" + Thread.currentThread().getName()); Thread thread = new Thread(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("[" + System.currentTimeMillis() + "] " + Thread.currentThread().getName() + " 对 threadLocal 赋值"); threadLocal.set("Shawearn 你好, 我是" + Thread.currentThread().getName()); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("[" + System.currentTimeMillis() + "] " + Thread.currentThread().getName() + " 从 threadLocal 取值 【" + threadLocal.get() + "】"); threadLocal.remove(); }, "子线程"); thread.start(); thread.join(); System.out.println("[" + System.currentTimeMillis() + "] " + Thread.currentThread().getName() + " 从 threadLocal 取值 【" + threadLocal.get() + "】"); threadLocal.remove(); }

上面的代码执行流程如下:
  1. 新建一个 ThreadLocal 对象 threadLocal;
  2. 主线程写入数据到 threadLocal;
  3. 子线程写入数据到 threadLocal;
  4. 子线程从 threadLocal 读取数据;
  5. 主线程从 threadLocal 读取数据;
【Java|从源码层面谈谈 ThreadLocal 线程私有实现方式】上面的代码中主线程与子线程访问的都是同一个 ThreadLocal 对象实例,可能会认为步骤 5 时主线程从 threadLocal 读取的数据是子线程写入的数据,然而事实并非如此,我们看程序执行结果:
[1595303916102] 主线程 对 threadLocal 赋值 [1595303917103] 子线程 对 threadLocal 赋值 [1595303918104] 子线程 从 threadLocal 取值 【Shawearn 你好, 我是子线程】 [1595303918104] 主线程 从 threadLocal 取值 【Shawearn 你好, 我是主线程】

虽然子线程与主线程访问的是同一个 ThreadLocal 实例,然而子线程与子线程对 ThreadLocal 的操作是互不干扰的,也即是线程私有。
如何实现线程私有 下面一起通过源码分析 ThreadLocal 是如何实现线程私有的,为了方便理解,笔者对代码标明步骤,并加了中文注释;
先看 ThreadLocal 内部的 get/set 方法:
set 方法
/** * Sets the current thread's copy of this thread-local variable * to the specified value.Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of *this thread-local. */ public void set(T value) { // 赋值步骤 1. 获取当前线程对象; Thread t = Thread.currentThread(); // 赋值步骤 2. 根据当前线程对象获取 ThreadLocalMap 对象; ThreadLocalMap map = getMap(t); // 赋值步骤 3. 赋值操作; if (map != null) { // 赋值步骤 3-1. 若 map 不为空,直接以当前 ThreadLocal 对象为 key,将值存放到 map 中; map.set(this, value); } else { // 赋值步骤 3-2. 若 map 为空,创建 map 并以当前 ThreadLocal 对象为 key,将值存放到 map 中; createMap(t, value); } }

get 方法
/** * Returns the value in the current thread's copy of this * thread-local variable.If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { // 获取当前线程对象; Thread t = Thread.currentThread(); // 根据当前线程对象获取 ThreadLocalMap 对象; ThreadLocalMap map = getMap(t); if (map != null) { // 若 map 不为空,根据当前 ThreadLocal 获取数据; ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 若 map 为空,初始化默认值并返回; return setInitialValue(); }

嗯,到这里,上面的代码如果能看懂,就可以不往下看了,你的阅读量我已收下,哈哈。
赋值分析 赋值步骤 1 由上面的代码可知,调用 ThreadLocal 的 set 方法赋值时,程序先获取当前线程对象,这个没什么好讲的;
赋值步骤 2 根据当前线程对象获取 ThreadLocalMap 对象,对 ThreadLocal 读写的数据实际上存放在 ThreadLocalMap 实例中,getMap 方法实现如下:
/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @paramt the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { // 步骤 2-1. 返回了线程对象中的 threadLocals 变量,返回结果可能为 null; return t.threadLocals; }

返回了线程对象 t 中的 threadLocals 变量,t 即为当前的线程对象;
查看 java.lang.Thread 源码中对于 threadLocals 的定义如下:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;

根据 Thread 中对 threadLocals 的描述,threadLocals 由 ThreadLocal 进行维护——Thread 不负责 threadLocals 值的初始化,因此 ThreadLocal.getMap 返回的可能是一个指向 null 的对象,也可能是一个不为 null 的 ThreadLocalMap 对象;
赋值步骤 3 继续回到 ThreadLocal.set 方法,调用 getMap 方法(步骤 2)之后,程序对 map 进行赋值操作,赋值分两种情况—— map 不为空以及 map 为空:
若 map 不为 null,以当前 ThreadLocal 对象实例为 key,将新值存放到 map 中,赋值成功;
若 map 为 null,调用 createMap 方法新建 ThreadLocalMap 对象,以当前 ThreadLocal 对象实例为 key 进行赋值,然后将 t.threadLocals 的引用指向新建的 ThreadLocal 对象,赋值成功,createMap 方法代码如下:
/** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

取值分析 理解了 ThreadLocal 的赋值过程,取值的过程也就好理解了,此处直接说过程,不进行代码分析:
  1. 获取当前线程对象 t;
  2. 根据当前线程对象获取 ThreadLocalMap 对象 map;
  3. 判断 map 是否为 null;
    • map 为 null 时进行默认的初始化,以当前 ThreadLocal 对象实例为 key,以 null 为 value,初始化以后,从 ThreadLocalMap 对象中取值并返回;
    • map 不为 null 时,直接从 ThreadLocalMap 对象中取值并返回;
结论 虽然多个线程对同一个 ThreadLocal 对象进行读写,但实际读写的数据并不存在于 ThreadLocal 中,而是存放在 ThreadLocalMap 实例中,而 ThreadLocalMap 是线程 Thread 中的一个变量,因此,不同的线程持有不同的 ThreadLocalMap 对象,哪怕是对同一个 ThreadLocal 对象进行读写,只要他们是不同的线程,那么实际修改的只是各自线程中的 ThreadLocalMap 对象而已;

    推荐阅读