1. ThreadLocal介绍
1.1 基本介绍
ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是 private static类型的,用于关联线程和线程上下文。
我们可以得知ThreadLocal的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
总结:
- 线程并发:在多线程的场景下
- 传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
- 线程隔离:每个线程的变量都是独立的,不会互相影响
1.2 基本使用
1.2.1 常用方法
方法声明
描述
ThreadLocal()
构造方法,用于创建对象
public void set(T value)
设置当前线程绑定的局部变量
public T get()
获取当前线程绑定的局部变量
public void remove()
删除当前线程绑定的局部变量(注意:该操作特别重要,防止内存溢出)
1.2.2 使用案例
package xyz.xiaolinz.demo1;
/**
*
* @author huangmuhong
* @version 1.0.0
* @date 2023/1/12
*
*
* ThreadLocal:
* 1. set(): 给当前变量绑定一个值
* 2. get(): 获取当前变量的值
**/
public class ThreadLocalDemo {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
final ThreadLocalDemo demo = new ThreadLocalDemo();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
demo.set(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "====>" + demo.get());
// 使用完成时显示调用remove()方法
demo.remove();
}).start();
}
}
/**
* 绑定变量到当前线程
*
* @param value 值
*/
public void set(String value) {
threadLocal.set(value);
}
/**
* 获取当前线程绑定的变量
*
* @return 值
*/
public String get() {
return threadLocal.get();
}
/**
* 去除绑定变量
* 该方法不是必须的,因为ThreadLocalMap中的Entry是使用弱引用的,所以当线程结束时,会自动被回收,不会造成内存泄漏
* 但是当其他对象持有ThreadLocal的引用时,就需要手动调用该方法去除绑定变量,否则会造成内存泄漏
*/
public void remove() {
threadLocal.remove();
}
}
1.3 ThreadLocal类与synchronized关键字
1.3.1 synchronized同步方式
synchronized简单案例
package xyz.xiaolinz.demo2.demo1;
/**
*
* @author huangmuhong
* @version 1.0.0
* @date 2023/1/12
*
*
* ThreadLocal:
* 1. set(): 给当前变量绑定一个值
* 2. get(): 获取当前变量的值
**/
public class SynchronizedDemo {
private String content;
public static void main(String[] args) {
final SynchronizedDemo demo = new SynchronizedDemo();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (demo) {
demo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "====>" + demo.getContent());
}
}).start();
}
}
/**
*
*
* @return 值
*/
public String getContent() {
return content;
}
/**
*
*
* @param value 值
*/
public void setContent(String value) {
content = value;
}
}
结果打印
![[Untitled 13.png]]
从结果可以发现,加锁的方案同样也可以实现线程隔离的效果,但是在代码中加锁会导致程序失去了并发性,程序只能阻塞的等待前一个线程将demo对象的锁释放后才能继续去操作content的值。所以给该方案降低了程序的性能
1.3.2 ThreadLocal与synchronized的区别
虽然ThreadLocal模式与synchronized关键字都用于处理多线程并发访问变量的问题,不过两者处理的角度和思路不同
synchronized
ThreadLocal
原理
同步机制采用了“以时间换空见”的方式,只提供了一份变量,让不同的线程派对访问
ThreadLocal采用“以空间换时间”的方式,为每个线程都提供了一份变量父本,从而实现同时访问而互不干扰
侧重点
多个线程间访问资源的同步(阻塞)
多线程中让每个线程之间的数据相互隔离
总结:在刚刚的案例中,ThreadLocal 和 synchronized 都能解决问题,但是使用 ThreadLocal 是最佳选择,我们在开发中通长都会选择以“空间换时间”的方案将性能最大化,但是使用 ThreadLocal 需要注意内存泄漏的问题
2. ThreadLocal的内部结构
2.1 常见的误解
如果外面恩不去看源代码的话,可能会猜测ThreadLocal是这样子设计的:每个ThreadLocal都创建一个Map,然后用线程作为Map的key,要存储的局部比昂量作为Map的value,这样就能达到各个线程的局部变量隔离的效果。这是最简单的设计方法,JDK最早期的ThreadLocal确实是这样设计的,但现在早已不是了。
![[Untitled 12.png]]
2.2 现在的设计
但是,JDK后面优化了设计方案,在JDK8中 ThreadLocal
的设计是:每个 Thread
维护一个 ThreadLocalMap
,这个Map的key
是 ThreadLocal
本身,value
擦汗is真正要存储的值 Object
。
具体的过程是这样的:
- 每个Thread线程内部都有一个Map(ThreadLocalMap)
- Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
- Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值
- 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本隔离,互不干扰
![[Untitled 11.png]]
2.3 JDK8后设计的好处
- 每个Map存储的Entry数量变少
- 当Thread销毁时,ThreadLocalMap也会随之销毁,减少内存的使用
3. ThreadLocal的核心方法源码
基于ThreadLocal的内部结构,我们继续分析核心源码,深入了解原理;
方法:
方法声明
描述
protected T initalValue()
返回当前线程局部变量的初始值
public void set(T set)
设置当前线程绑定的局部变量
public T get()
获取当前线程绑定的局部变量
public void remove()
移除当前线程绑定的局部变量
3.1 set方法
/**
* 设置当前线程对应的ThreadLocal的值
* @param value 将要保存在当前线程对应的ThreadLocal的值
*/
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取到线程维护的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 往map填充key为ThreadLocal,value为value副本变量的记录
// 可以看到ThreadLocal本身并不存储任何变量的副本值,其存在更像操作ThreadLocalMap的工具类
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
/**
* 获取当前线程Thread对象维护的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 创建ThreadLocalMap,用于在当前线程(Thread)维护ThreadLocalMap为空的情况下使用
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
代码执行流程
- 首先获取当前线程,并根据当前线程获取一个ThreadLocalMap
- 如果获取的Map不为空,则将参数设置到Map钟(当前的ThreadLocal为key)
- 如果Map为空,则给该线程直接new一个ThreadLocalMap,并将值记录
3.2 get方法
/**
* 返回当前线程中保存着ThreadLocal的值
* 如果当前线程没有刺ThreadLocal变量
* 则它会通过调用{@link [[initialvalue]]}方法进行初始化值
*
*/
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 判断map存不存在
if (map != null) {
// 以当前ThreadLocal为key,调用getEntry获取对应的存储实体
ThreadLocalMap.Entry e = map.getEntry(this);
// 对结果实体进行判空
if (e != null) {
@SuppressWarnings("unchecked")
// 实际结果
T result = (T)e.value;
return result;
}
}
/*
初始化:有两种情况执行当前代码
第一种情况:map不存在,表示此线程没有维护的ThreadLocalMap对象
第二种情况:map存在,但是没有与当前ThreadLocal关联的Entry
*/
return setInitialValue();
}
/**
* 当读取不到对应值时使用当前方法初始化
*/
private T setInitialValue() {
// 调用initialValue方法获取初始值
// 此方法可以被子类重写,如果不重写返回null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
代码执行流程
- 首先获取当前线程,根据当前线程获取一个Map
- 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry,否则到4
- 如果e不为null,则返回e.value 否则转到4
- Map为空或者e为空,则通过initialValue函数获取初始值Value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新Map
评论区