欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-29 17:29:48
前言ThreadLocal 是一个用于存取线程本地变量的类,通过其实例的 get/set 方法进行数据的存取,数据存取到 ThreadLocal 后,只有线程自身能访问到,如下图:线程私有示例代码先看一段代码:public static void main(String[] args) throws InterruptedException { ThreadLocal threadLocal = new ThreadLocal<>(); T...

前言

ThreadLocal 是一个用于存取线程本地变量的类,通过其实例的 get/set 方法进行数据的存取,数据存取到 ThreadLocal 后,只有线程自身能访问到,如下图:

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

线程私有示例代码

先看一段代码:

public static void main(String[] args) throws InterruptedException {
    ThreadLocal<String> 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 读取数据;

上面的代码中主线程与子线程访问的都是同一个 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.
 *
 * @param  t 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 对象而已;

本文地址:https://blog.csdn.net/shawearn1027/article/details/107487510