问题

由于线程池的创建参数并不依赖当前线程,而且核心线程不会消失,因此应当将线程与主线程视为无关,所以ThradLocal处理的Thread.threadLocalsInheritableThreadLocal处理的Thread.inheritableThreadLocals对于线程池都是无效的。
因此为了解决ThreadLocal的传输问题,就不能用InheritableThreadLocal的思想。

本文的思路是既然不能直接线程相关,那么我们是可以把ThreadLocal中的数据拷贝下来,然后在新线程执行的时候设置到新线程中,这样实现传输,原理如下

首先定义一个Runnable用来封装真正要执行的RunnableThreadLocal
然后在Run的时候用装饰器模式先执行ThreadLocal的设置,然后再执行原本方法,最后再移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//线程池封装Runnable
public class ExecutorThreadLocalRunnableWrapper<T> implements Runnable {
Runnable runnable;
ThreadLocal<T> local;

T context;

public ExecutorThreadLocalRunnableWrapper(Runnable runnable, ThreadLocal<T> local) {
this.runnable = runnable;
this.local = local;
this.context = local.get();
}

@Override
public void run() {
local.set(context);
try {
runnable.run();
} finally {
local.remove();
}
}
}

在线程使用的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void t2() throws InterruptedException {

ThreadLocal<String> local = new ThreadLocal<>();

ExecutorService executorService = Executors.newFixedThreadPool(2);

for (int i = 0; i < 10; i++) {
local.set("" + i);
executorService.execute(
//创建Runner
new ExecutorThreadLocalRunnableWrapper<String>(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行真正方法,获取local" + local.get());
}

},
//这里传入local
local
));
}
System.out.println("主线程准备结束");
Thread.sleep(1000);
}

输出结果为

1
2
3
4
5
6
7
8
9
10
11
主线程准备结束
pool-1-thread-2执行真正方法,获取local1
pool-1-thread-1执行真正方法,获取local0
pool-1-thread-2执行真正方法,获取local2
pool-1-thread-1执行真正方法,获取local3
pool-1-thread-2执行真正方法,获取local4
pool-1-thread-1执行真正方法,获取local5
pool-1-thread-2执行真正方法,获取local6
pool-1-thread-1执行真正方法,获取local7
pool-1-thread-2执行真正方法,获取local8
pool-1-thread-1执行真正方法,获取local9

没有重复的,也说明了设置获取都正确了这样就完成了ThreadLocal的传递

PS: 这里本来想用反射拿到Thread中的ThreadLocalMap,然后再设置ThreadLocalMap,但是测试之后实际上是不行的,会出错,暂时未去找原因

若要有更完美的实现,建议使用alibab开源的transmittable-thread-localTTL
不需要传ThreadLocal实例等