ThreadLocal及其扩展
ThreadLocal是JDK提供的,是线程的本地变量,如果创建一个ThreadLocal变量,那么所有访问这个变量的线程都会有这个变量的一个本地副本。
ThreadLocal可以理解为是一个全局的初始变量,但是每个线程访问该变量的后续操作,都是对自身副本的操作,规避了线程安全问题。
一. ThreadLocal
1.Thread,ThrealLocal,ThrealLocalMap的关系?
首先,每个Thread内部会维护一个threadLocals变量,这个变量的类型是ThrealLocal.ThreadLocalMap;而ThreadLocalMap是ThreadLocal的一个内部类;对ThreadLocal进行数据操作实际上是对ThreadLocalMap进行数据操作
2. API分析
2.1 ThreadLocal.set(T value);
//1. 对ThreadLocal进行赋值操作
public void set(T value) {
// 获取当前操作该set()方法的线程,这也好理解,所有线程都是操作各自的ThreadLocal变量;
Thread t = Thread.currentThread();
// 获取当前线程内部的ThreadLocalMap变量map
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果该变量值不为空,则赋值,key为当前ThreadLocal自身;
map.set(this, value);
else
// 如果变量值为空,则创建一个ThreadLocalMap变量
createMap(t, value);
}
//2. 创建ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
// 如果线程没有map变量,则新建一个以当前ThreadLocal为key的一个map对象
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
由此可见,所有对ThreadLocal的操作,实际上都是对内部ThreadLocalMap的操作
2.2 ThreadLocal.get()
// 1. 获取数据
public T get() {
// 获取当前操作该get()方法的线程
Thread t = Thread.currentThread();
// 带着当前线程,调用getMap方法,返回该线程内部维护的map对象;
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 如果有值,则根据key获取结果并返回。
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果map值为空,则返回一个初始化值
return setInitialValue();
}
// 2. getMap(Thread t)方法
ThreadLocalMap getMap(Thread t) {
// 返回线程自身的map变量
return t.threadLocals;
}
// 3. setInitialValue()初始化方法
private T setInitialValue() {
T value = initialValue();// 默认获取null值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 返回当前线程自身的map变量
if (map != null)
map.set(this, value); // 如果map变量不为空,则赋值
else
createMap(t, value); // 如果map为空,则走createMap逻辑;
return value;
}
// 4.initialValue()方法,默认返回一个null值
protected T initialValue() {
return null;
}
2.3 ThreadLocal.remove()
// 移除数据
public void remove() {
// 获取当前线程内部的ThreadLocalMap类型的变量,如果不为空,则根据key移除数据;
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
3. 代码思考
public class Test {
// 设置一个初始化的ThreadLocal对象,所有线程对它的操作都是操作各自的副本
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 主线程塞值
threadLocal.set(1);
System.out.println("操作主线程后的值:"+threadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
// 子线程取值
System.out.println("子线程 s查询数据:"+threadLocal.get());
}
}).start();
}
}
// 运行结果
操作主线程后的值:1
子线程查询数据:null
由此可见,每个线程对ThreadLocal的操作都是基于各自的副本,互不干扰,那么有些场景,父子线程需要进行数据传递(例如链路调用,下载中心异步调用等等),那么就需要在子线程中能拿到父线程的值,这个时候ThreadLocal就不适用,InheritableThreadLocal的出现,则是为了解决这个问题!
二. InheritableThreadLocal
InheritableThreadLocal继承自ThreadLocal,主要为了让子线程可以使用父线程中的本地变量。
-
先看效果
其他不变,单独改成InheritableThreadLocal,发现子线程中本地变量也能拿到父线程中的值。 -
看看原因
- 首先,Thread线程中包括两个变量,threadLocals与inheritableThreadLocals。
-
看看线程的创建流程
// 1. 线程的构造函数 public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } // 2. 初始化方法 private void init(ThreadGroup g, Runnable target, String name, long stackSize) { //内部再调用一个init初始化方法,注意参数true init(g, target, name, stackSize, null, true); } // 3.内部init方法 private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { // 获取父线程 Thread parent = currentThread(); // 省略部分代码,看关键点,此处逻辑是,如果inheritThreadLocals为true(默认true),并且父线程中的 inheritableThreadLocals变量不为空,则将它赋值给当前线程的inheritableThreadLocals变量不为空变量 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); }
-
到此,结合测试代码,就理解了。我们在主线程塞值,然后new一个线程,此时,上述流程中可得,子线程中的inheritableThreadLocals变量的值就等于主线程中塞的值,所以子线程操作inheritableThreadLocals变量时,能拿到主线程中的值,完成了父子线程中的数据传递。
3. 再思考
上述流程,我们可以发现,多线程中,只要使用InheritableThreadLocal,就可以完成父子线程数据传递,因为在new线程时,间接的完成了赋值操作,那么,如果类似线程池这种,用的是缓存线程,没有直接new线程时,如何完成数据传递呢?
三. TransmittableThreadLocal
TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。若希望 TransmittableThreadLocal 在线程池与主线程间传递,需配合 TtlRunnable 和 TtlCallable 使用。— 此处引用TransmittableThreadLocal详解,TransmittableThreadLocal使用及原理解析
后续继续补充。。。。。。