前言 ThreadLocal 是一个用于存取线程本地变量的类,通过其实例的 get/set 方法进行数据的存取,数据存取到 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();
}
上面的代码执行流程如下:
- 新建一个 ThreadLocal 对象 threadLocal;
- 主线程写入数据到 threadLocal;
- 子线程写入数据到 threadLocal;
- 子线程从 threadLocal 读取数据;
- 主线程从 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 的赋值过程,取值的过程也就好理解了,此处直接说过程,不进行代码分析:
- 获取当前线程对象 t;
- 根据当前线程对象获取 ThreadLocalMap 对象 map;
- 判断 map 是否为 null;
- map 为 null 时进行默认的初始化,以当前 ThreadLocal 对象实例为 key,以 null 为 value,初始化以后,从 ThreadLocalMap 对象中取值并返回;
- map 不为 null 时,直接从 ThreadLocalMap 对象中取值并返回;
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 笔记|如何在Windows11安装安卓子系统()