有关ThreadLocal的介绍我之前一篇文章已经做了介绍:https://blog.csdn.net/qq_26012495/article/details/86475725
本篇主要解决,在父线程中开启子线程时ThreadLocal存在的值传递问题,以及如何解决。
目录
3. 修改为InheritableThreadLocal代码测试
4. InheritableThreadLocal在其他场景存在的问题
三、TransmittableThreadLocal(阿里开源)
1. 查看TransmittableThreadLocal源码
一、ThreadLocal
1. 普通ThreadLocal存在的问题
先使用普通的ThreadLocal类做一个测试,在main方法中通过new Thread方法来开启子线程,在子线程中打印父线程中set的ThreadLocal值。
public static void main(String[] args) throws Exception {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("初始化的值能继承吗?");
System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程到了");
System.out.println("子线程的ThreadLocal值:" + threadLocal.get());
}
}).start();
}
子线程打印的结果为null,说明父线程的ThreadLocal值在子线程中并未得到传递,而是中断了。
二、InheritableThreadLocal
1. InheritableThreadLocal源码分析
由于ThreadLocal父线程无法传递本地变量到子线程中,于是JDK引入了InheritableThreadLocal类,该类的首部描述:该类继承了ThreadLocal类,用以实现父线程到子线程的值继承;创建子线程时,子线程将接收父线程具有值的所有可继承线程局部变量的初始值。通常子线程的值与父线程相同;子线程的值可以被父线程重写的方法改变;
/**
* This class extends <tt>ThreadLocal</tt> to provide inheritance of values
* from parent thread to child thread: when a child thread is created, the
* child receives initial values for all inheritable thread-local variables
* for which the parent has values. Normally the child's values will be
* identical to the parent's; however, the child's value can be made an
* arbitrary function of the parent's by overriding the <tt>childValue</tt>
* method in this class.
*
* <p>Inheritable thread-local variables are used in preference to
* ordinary thread-local variables when the per-thread-attribute being
* maintained in the variable (e.g., User ID, Transaction ID) must be
* automatically transmitted to any child threads that are created.
*
* @author Josh Bloch and Doug Lea
* @see ThreadLocal
* @since 1.2
*/
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
共有如下三个方法,没有什么特殊的地方:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* Computes the child's initial value for this inheritable thread-local
* variable as a function of the parent's value at the time the child
* thread is created. This method is called from within the parent
* thread before the child is started.
* <p>
* This method merely returns its input argument, and should be overridden
* if a different behavior is desired.
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* Get the map associated with a ThreadLocal.
*
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* Create the map associated with a ThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the table.
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
我们以最简单的创建线程为例
Thread thread = new Thread(...)
2. Thread类源码分析
通过查看Thread类源码,其有两个全局变量
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Thread构造器,会调用init方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
再看init方法,对inheritableThreadLocal变量进行了初始化,将当前线程的inheritableThreadLocal赋值给子线程的inheritableThreadLocal,完成了父线程到子线程的变量传递。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
Thread parent = currentThread();
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
3. 修改为InheritableThreadLocal代码测试
public static void main(String[] args) throws Exception {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("初始化的值能继承吗?");
System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程到了");
System.out.println("子线程的ThreadLocal值:" + threadLocal.get());
}
}).start();
}
运行结果,可看到在new Thread创建子线程的场景中,子线程已经成功打印了父线程的ThreadLocal值。
4. InheritableThreadLocal在其他场景存在的问题
但是InheritableThreadLocal仍然存在一个问题,上述我们解决了在创建Thread时的传递,其传递时机是在创建线程时;但在线程池复用的情景中,没有创建Thread的触发条件。
举一个失败案例:创建一个固定大小为2的线程池,进行五次子线程执行,每次给父线程的threadLocal重新赋值,由于线程池大小为2,因此只有前两次触发初始化线程池,后面三次均为线程复用,理论上来说,子线程可继承的父线程变量到i=1截止。
public static void main(String[] args) throws Exception {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
ExecutorService threadPool = Executors.newFixedThreadPool(2);// 创建一个固定大小为2的线程池
for (int i = 0; i < 5; i++) {
threadLocal.set("初始化的值能继承吗?" + i);
System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("子线程到了");
System.out.println("=========子线程的ThreadLocal值:" + threadLocal.get());
}
});
}
}
运行结果如我所料,子线程打印的值只有0和1
三、TransmittableThreadLocal(阿里开源)
使用线程池时,ThreadLocal父线程到子线程值传递问题需要TransmittableThreadLocal来解决。TransmittableThreadLocal是阿里开源的专门用以解决上述问题的类,使用时需要引入maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.4</version>
</dependency>
TTL:https://github.com/alibaba/transmittable-thread-local#-%E9%9C%80%E6%B1%82%E5%9C%BA%E6%99%AF
1. 查看TransmittableThreadLocal源码
其继承了InheritableThreadLocal类,因此同样也支持new Thread时的值传递;
其中set和get方法均调用了addValue方法,addValue方法使用了全局的holder变量,将this指针put入holder中,this指的就是当前线程的ThreadLocal【因为ThreadLocal维护了线程和value的关系,不论是父线程还是子线程,都会被存入ThreadLocalMap中,因此此处的this包含了所有的父线程+子线程】。
holder就是一个InheritableThreadLocal变量,该变量在子线程的执行过程中,缓存了所有线程的TransmittableThreadLocal(包括子线程+父线程),set方法缓存父线程,get方法缓存子线程(多数情况都是父线程赋值,子线程调用,所以不论是get还是set方法都要给holder存值)。
public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> {
static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder = new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
return new WeakHashMap();
}
protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
return new WeakHashMap(parentValue);
}
};
public final void set(T value) {
super.set(value);
if (null == value) {// 提供set null移除value的功能
this.removeValue();
} else {
this.addValue();
}
}
public final T get() {
T value = super.get();
if (null != value) {
this.addValue();
}
return value;
}
void addValue() {
if (!((Map)holder.get()).containsKey(this)) {
((Map)holder.get()).put(this, (Object)null);
}
}
}
2. TtlExecutors类使用及源码分析
上述问题在于线程池中线程复用没有触发条件,而只要在子线程执行时加入复制父线程的threadLocal即可。而原生的线程池创建方法不提供此功能,因此需要单独的线程池创建类支持,TtlExecutors,同样也是阿里开源的。
使用场景如下:
ExecutorService threadPool= TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(poolSize));
修改刚才的代码,ThreadLocal和Executors修改为阿里的 TransmittableThreadLocal和TtlExecutors:
public static void main(String[] args) throws Exception {
ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
ExecutorService threadPool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
for (int i = 0; i < 5; i++) {
threadLocal.set("初始化的值能继承吗?" + i);
System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("子线程到了");
System.out.println("=========子线程的ThreadLocal值:" + threadLocal.get());
}
});
}
}
再来看TtlExecutors类,其getTtlExecutorService方法如下,返回的是ExecutorServiceTtlWrapper类
public static ExecutorService getTtlExecutorService(ExecutorService executorService) {
return (ExecutorService)(executorService != null && !(executorService instanceof ExecutorServiceTtlWrapper) ? new ExecutorServiceTtlWrapper(executorService) : executorService);
}
再去看ExecutorServiceTtlWrapper,该类中未寻找到execute方法,因此在其父类ExecutorTtlWrapper中
class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService {
execute方法中,执行Runnable时,外层包装了TtlRunnable类
class ExecutorTtlWrapper implements Executor {
final Executor executor;
public ExecutorTtlWrapper(Executor executor) {
this.executor = executor;
}
public void execute(Runnable command) {
this.executor.execute(TtlRunnable.get(command));
}
}
看下TtlRunnable类的run方法(多线程的执行处,到达run方法时,子线程内部逻辑已经执行完毕,包括子线程的ThreadLocal的get或者set),发现以下4句是关键语句,正常run方法执行逻辑应该只有3,TtlRunnable的run实际上是在正常run的前后包装了逻辑:
- 一进入TtlRunnable就将当前的所有TransmittableThreadLocal存储在AtomicReference(copied)中
- 在子线程执行前,将copied(即1获取的)备份在backup中,copied中不存在的,holder中存在的,是刚刚加入的子线程threadLocal,把copied的全部remove掉,只留下该子线程的。
- 执行子线程(runnable的类是Runnable)
- 在子线程执行后,把刚刚执行的threadLocal清理掉,再把backup中的set回来,完成一次神操作。
public final class TtlRunnable implements Runnable {
private final AtomicReference<Map<TransmittableThreadLocal<?>, Object>> copiedRef = new AtomicReference(TransmittableThreadLocal.copy());// 1
public void run() {
Map<TransmittableThreadLocal<?>, Object> copied = (Map)this.copiedRef.get();
if (copied != null && (!this.releaseTtlValueReferenceAfterRun || this.copiedRef.compareAndSet(copied, (Object)null))) {
Map backup = TransmittableThreadLocal.backupAndSetToCopied(copied);// 2
try {
this.runnable.run();// 3
} finally {
TransmittableThreadLocal.restoreBackup(backup);// 4
}
} else {
throw new IllegalStateException("TTL value reference is released after run!");
}
}
以下是2和4的源码部分
static Map<TransmittableThreadLocal<?>, Object> backupAndSetToCopied(Map<TransmittableThreadLocal<?>, Object> copied) {
Map<TransmittableThreadLocal<?>, Object> backup = new HashMap();
Iterator iterator = ((Map)holder.get()).entrySet().iterator();
Entry entry;
TransmittableThreadLocal threadLocal;
while(iterator.hasNext()) {
entry = (Entry)iterator.next();
threadLocal = (TransmittableThreadLocal)entry.getKey();
backup.put(threadLocal, threadLocal.get());// 执行备份
if (!copied.containsKey(threadLocal)) {// 从holder中remove掉copied中不存在的
iterator.remove();
threadLocal.superRemove();
}
}
iterator = copied.entrySet().iterator();
while(iterator.hasNext()) {
entry = (Entry)iterator.next();
threadLocal = (TransmittableThreadLocal)entry.getKey();
threadLocal.set(entry.getValue());
}
doExecuteCallback(true);
return backup;
}
static void restoreBackup(Map<TransmittableThreadLocal<?>, Object> backup) {
doExecuteCallback(false);
Iterator iterator = ((Map)holder.get()).entrySet().iterator();
Entry entry;
TransmittableThreadLocal threadLocal;
// 把刚刚执行的那个清理掉,执行完不需要了,然后再把backup里面的拿回来,恢复初始状态
while(iterator.hasNext()) {
entry = (Entry)iterator.next();
threadLocal = (TransmittableThreadLocal)entry.getKey();
if (!backup.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
iterator = backup.entrySet().iterator();
while(iterator.hasNext()) {
entry = (Entry)iterator.next();
threadLocal = (TransmittableThreadLocal)entry.getKey();
threadLocal.set(entry.getValue());
}
}
四、总结
ThreadLocal
解决问题:
维护了线程和value的关系
存在问题:
无法解决父线程向子线程传递ThreadLocal值的问题
InheritableThreadLocal
解决问题:
JDK提供的,在通过new Thread创建子线程时,复制父线程的ThreadLocal值至子线程,触发开关为创建全新的子线程。
存在问题:
但线程池的线程复用机制打破了创建全新子线程的时机,导致子线程继承到错误的父线程ThreadLocal(还是复用前的子线程那个值)
TransmittableThreadLocal
解决问题:
阿里开源的TTL包,配合TtlExecutors、TtlRunnable、TtlCallable实现在子线程执行时的ThreadLocal值传递。
解决思路:
ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
ExecutorService threadPool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
for (int i = 0; i < 5; i++) {
threadLocal.set("初始化的值能继承吗?" + i);
System.out.println("父线程的ThreadLocal值:" + threadLocal.get());
threadPool.execute(new Runnable() {// 初始化TtlRunnable
@Override
public void run() {
System.out.println("子线程到了");
System.out.println("=========子线程的ThreadLocal值:" + threadLocal.get());
}
});
}
对照着刚才的示例代码
- 在每一次调用threadLocal.set方法,都将当前的ThreadLocal存入holder的key中,值为null;
- threadPool.execute方法将Runnable包装成TtlRunnable,调用该方法时,还没进入子线程,完成TtlRunnable的初始化,TtlRunnable的初始化会将当前所有已经产生的ThreadLocal缓存;
- 未完待续