<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
ThreadLocal
用于线程隔离,常用与纪录用户登陆状态、事务状态等。在链路追踪中,ThreadLocal
也可用于记录TID,但通常遇到线程池时,即时使用InheritableThreadLocal (ITL)
时TID无法被传递到执行线程中,归根结底是因为线程会被复用,而ITL
只能在线程创建时继承父类线程的变量,而在任务执行时,父类这些变量可能已经变化了,这时是无法同步到ITL
的。
问题案例
import com.alibaba.ttl.TransmittableThreadLocal;
...
public class TTLTest{
// 此时相当于一个普通的InheritableThreadLocal
static TransmittableThreadLocal<String> ctx = new TransmittableThreadLocal<>();
public static void main() throws InterruptedException {
// 2个线程
ExecutorService es = Executors.newFixedThreadPool(2);
// 初始设置
ctx.set("1");
System.out.println("set 1 success");
BizRunnable bizRunnable = new BizRunnable();
// 第一个线程创建,此时TTL当作ITL使用时新线程的ctx为1
es.execute(bizRunnable);
// 再次设置
ctx.set("2");
System.out.println("set 2 success");
// 第二个线程创建,此时TTL当作ITL使用时新线程的ctx为2
es.execute(bizRunnable);
for (int i = 0; i < 20; i++) {
// 模拟复用,第一个线程的ctx仍然是创建时的1而不是2
es.execute(bizRunnable);
}
}
public static class BizRunnable implements Runnable{
@Override
public void run() {
String s = ctx.get();
System.out.println("get : "+s);
}
}
}
运行结果:
set 1 success
set 2 success
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 2
get : 1
get : 2
get : 1
正确案例
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
...
public TTLTest{
public static void main() throws InterruptedException {
// 2个线程
ExecutorService es = Executors.newFixedThreadPool(2);
// 初始设置
ctx.set("1");
System.out.println("set 1 success");
BizRunnable old = new BizRunnable();
// com.alibaba.ttl.TtlRunnable 包装原来的Runnable
TtlRunnable bizRunnable = TtlRunnable.get(old);
// 第一个线程创建,此时TTL当作ITL使用时新线程的ctx为1
es.execute(bizRunnable);
// 改动无效,无法被传递到线程池线程
ctx.set("2");
System.out.println("set 2 success");
// 第二个线程创建,此时TTL当作ITL使用时新线程的ctx为2
// 第一个线程ctx同时被设为1
// 每次调用都要重新包装
bizRunnable = TtlRunnable.get(old);
es.execute(bizRunnable);
for (int i = 0; i < 20; i++) {
// 模拟复用
es.execute(bizRunnable);
}
}
public static class BizRunnable implements Runnable{
@Override
public void run() {
String s = ctx.get();
System.out.println("get : "+s);
}
}
}
运行结果:
set 1 success
set 2 success
get : 1
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
TTL
的使用就是包装提交的Runnable
或Callable
或线程池,注意:包装Runnable
或Callable
时,每次提交都要重新包装,这样很麻烦,可以直接包装线程池,或者使用Spring提供的ThreadPoolTaskExecutor
,该类会对提交的Runnable
进行包装:
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(8);
executor.setCorePoolSize(3);
executor.setThreadNamePrefix("prefix-");
executor.setQueueCapacity(200);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
// 设置包装器
executor.setTaskDecorator(TtlRunnable::get);
executor.initialize();