从源码层面谈谈 ThreadLocal 线程私有实现方式
前言
ThreadLocal 是一个用于存取线程本地变量的类,通过其实例的 get/set 方法进行数据的存取,数据存取到 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();
}
上面的代码执行流程如下:
- 新建一个 ThreadLocal 对象 threadLocal;
- 主线程写入数据到 threadLocal;
- 子线程写入数据到 threadLocal;
- 子线程从 threadLocal 读取数据;
- 主线程从 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 的赋值过程,取值的过程也就好理解了,此处直接说过程,不进行代码分析:
- 获取当前线程对象 t;
- 根据当前线程对象获取 ThreadLocalMap 对象 map;
- 判断 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
上一篇: JVM的核心内容
下一篇: 立秋不吃啥?立秋之后适合吃啥?