ThreadLocal使用
用于相同线程内上下文的传递,避免显式传参,简化代码。
比如controller层把用户信息set到ThreadLocal,在service层get获取,无需显式传参。
ThreadLocal原理
Thread类中有一个threadLocals变量,类型为ThreadLocalMap
public class Thread {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap是一个自定义的hash map,底层是Entry数组,提供了getEntry(ThreadLocal<?> key) 、set(ThreadLocal<?> key, Object value)等api,使用线性探测法处理hash冲突。
从api可以看出,ThreadLocalMap的key是ThreadLocal对象,value是泛型对象值。
ThreadLocal的值,存储在Thread对象实例的堆内存空间里,而不是ThreadLocal对象,ThreadLocal只是提供了get set方法用于维护变量值。
InheritableThreadLocal使用
Thread类中,除了有threadLocals变量,还有一个inheritableThreadLocals变量
public class Thread {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
假设这样一个场景: 父线程开了一个子线程,但是我们希望在子线程中可以访问父线程中的ThreadLocal对象,也就是说有些数据需要进行父子线程间的传递。比如像这样:
public static void main(String[] args) {
ThreadLocal<Integer> threadLocal = new ThreadLocal();
for (int i = 0; i < 10; i++) {
// 父线程set
threadLocal.set(i);
new Thread(() -> {
// 子线程get
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
}).start();
}
}
执行结果
Thread-0: null
Thread-2: null
Thread-1: null
Thread-4: null
Thread-3: null
Thread-5: null
Thread-6: null
Thread-7: null
Thread-8: null
Thread-9: null
如果我们希望子线可以看到父线程的ThreadLocal,那么就可以使用InheritableThreadLocal
public static void main(String[] args) {
ThreadLocal<Integer> threadLocal = new InheritableThreadLocal();
for (int i = 0; i < 10; i++) {
// 父线程set
threadLocal.set(i);
new Thread(() -> {
// 子线程get
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
}).start();
}
}
再次执行
Thread-0: 0
Thread-2: 2
Thread-1: 1
Thread-3: 3
Thread-4: 4
Thread-5: 5
Thread-6: 6
Thread-7: 7
Thread-8: 8
Thread-9: 9
可以看到,每个子线程都可以访问到从父线程传递过来的一个数据。
InheritableThreadLocal原理
InheritableThreadLocal源码如下,可以看到InheritableThreadLocal重写了父类的几个方法。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
重写createMap方法是为了把变量存到Thread里的inheritableThreadLocals变量,而不是threadLocals变量。
Thread类的构造方法,会调用init方法。
调用子线程Thread类的init方法时,如果parent.inheritableThreadLocals不为空,会把parent.inheritableThreadLocals浅拷贝到子线程的inheritableThreadLocals.
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
实际业务里,InheritableThreadLocal的使用场景很少,主要原因如下
- 上下文的拷贝是发生在线程创建的时候,如果不是新建线程,而是用了线程池里的线程,就不行了,因为线程池的核心线程是提前创建的
- 父子线程的map里的value是同一个对象,如果这个对象本身不是线程安全的,那么就会有线程安全问题(父子线程同时访问)
TransmittableThreadLocal原理
阿里TransmittableThreadLocal依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.5</version>
</dependency>
如果使用线程池,有3个时机需要考虑: 创建子线程、提交任务、执行任务
- 线程池的核心线程在线程池初始化时就创建了,创建时机远远早于用户请求。
- 提交任务后,不一定会马上执行,有可能核心线程数用完了,需要在阻塞队列里等待。
- 最好是在执行任务前,把父线程的上下文拷贝到子线程。
假设一个请求打到tomcat,tomcat从线程池里取出线程A(父线程),在controller层设置了用户信息到线程A的ThreadLocal。然后进入service层,使用线程池,从线程池里拿出线程B执行业务逻辑。
线程A提交任务后,就可以处理别的请求了。
如果等到线程B执行任务前,才从线程A拷贝上下文,这时候线程A的ThreadLocal可能被其他请求的数据覆盖了。
因此,线程A提交任务时,就需要把上下文拷贝到一个地方,然后线程B执行任务前,再从那个地方拷贝上下文。
总结
ThreadLocal: 无法复制上下文至子线程中
InheritableThreadLocal: 创建子线程时,从父线程复制上下文
TransmittableThreadLocal: 子线程执行任务时,从父线程复制上下文
网友评论