一、inheritableThreadLocals的局限性
有时候比如记录日志,会有跨多线程的情况,并且很多情况下会使用到线程池,涉及到不同线程之间的传值,一般是父子线程,那就是使用 inheritableThreadLocals,但是它会有一个问题,即只会在线程第一次创建的时候进行拷贝。
ini
if (!attached) {
if ((characteristics & NO_INHERIT_THREAD_LOCALS) == 0) {
ThreadLocal.ThreadLocalMap parentMap = parent.inheritableThreadLocals;
if (parentMap != null && parentMap.size() > 0) {
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parentMap);
}
if (VM.isBooted()) {
this.contextClassLoader = contextClassLoader(parent);
}
} else if (VM.isBooted()) {
// default CCL to the system class loader when not inheriting
this.contextClassLoader = ClassLoader.getSystemClassLoader();
}
}
如果是线程池,如果改变了值,则子线程拿不到最新值了
csharp
public class ITLDemo {
private static final InheritableThreadLocal<String> ITL = new InheritableThreadLocal<>();
public static void main(String[] args) throws Exception {
System.out.println("===== 演示 ITL 局限 =====");
limitation();
}
private static void limitation() throws Exception {
ITL.set("v1");
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> System.out.println("limit-1: " + ITL.get()));
ITL.set("v2");
pool.submit(() -> System.out.println("limit-2: " + ITL.get()));
pool.shutdown();
}
}
===== 演示 ITL 局限 =====
v1
v1
可以看到,并没有及时进行更新。
就是因为在 Thread.java中,只有在创建的时候,才会拷贝一次。
二、使用TTL进行改造
csharp
private static final TransmittableThreadLocal<String> TTL= new TransmittableThreadLocal<String>();
private static void TTL() throws Exception {
TTL.set("v1");
ExecutorService pool = Executors.newFixedThreadPool(1);
pool = TtlExecutors.getTtlExecutorService(pool);
pool.submit(() -> System.out.println("TTL-1: " + TTL.get()));
Thread.sleep(1000);
// 这里变化了,但是 只会在第一次创建线程的时候进行拷贝
TTL.set("v2");
pool.submit(() -> System.out.println("TTL-2: " + TTL.get()));
pool.shutdown();
}
=======演示TTL=========
TTL-1: v1
TTL-2: v2
为什么TTL能实现?
1. 包装 ExecutorService
ini
这里是关键代码
ExecutorService pool = Executors.newFixedThreadPool(1);
pool = TtlExecutors.getTtlExecutorService(pool);
包装如下
less
@Nullable
@Contract(
value = "null -> null; !null -> !null",
pure = true
)
public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
return (ExecutorService)(!TtlAgent.isTtlAgentLoaded() && executorService != null && !(executorService instanceof TtlEnhanced) ? new ExecutorServiceTtlWrapper(executorService, true) : executorService);
}
本质上,是将当前的 ExecutorService 包装成了ExecutorServiceTtlWrapper
scala
@SuppressFBWarnings({"EQ_DOESNT_OVERRIDE_EQUALS"})
class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService, TtlEnhanced {
private final ExecutorService executorService;
ExecutorServiceTtlWrapper(@NonNull ExecutorService executorService, boolean idempotent) {
super(executorService, idempotent);
this.executorService = executorService;
}
2. 重写的 submit
将 task 包装成一个 TtlRunnable
less
@NonNull
public Future<?> submit(@NonNull Runnable task) {
return this.executorService.submit(TtlRunnable.get(task, false, this.idempotent));
}
3. 核心逻辑
typescript
public void run() {
Object captured = this.capturedRef.get();
if (captured != null && (!this.releaseTtlValueReferenceAfterRun || this.capturedRef.compareAndSet(captured, (Object)null))) {
Object backup = Transmitter.replay(captured);
try {
this.runnable.run();
} finally {
Transmitter.restore(backup);
}
} else {
throw new IllegalStateException("TTL value reference is released after run!");
}
}
4. 捕获 ThreadLocal
的值
ini
Object captured = this.capturedRef.get();
capturedRef
是一个AtomicReference
,用于存储捕获的ThreadLocal
值。captured
是当前线程捕获的ThreadLocal
值。
5. 检查捕获的值是否有效
kotlin
if (captured != null && (!this.releaseTtlValueReferenceAfterRun || this.capturedRef.compareAndSet(captured, (Object)null))) {
- 检查
captured
是否为null
。 - 如果
releaseTtlValueReferenceAfterRun
为false
,或者通过compareAndSet
成功将capturedRef
设置为null
,则继续执行。
-
releaseTtlValueReferenceAfterRun
是一个布尔标志,表示是否在任务执行后释放捕获的值。compareAndSet
用于原子性地将capturedRef
设置为null
,确保值只被释放一次。
6. 恢复 ThreadLocal
的值
ini
Object backup = Transmitter.replay(captured);
Transmitter.replay(captured)
方法将捕获的ThreadLocal
值恢复到当前线程。backup
是当前线程的原始ThreadLocal
值,用于在任务执行后恢复。
重点:threadLocal是怎么拷贝的
在TtlRunnable中Transmitter.capture()
kotlin
public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
private final AtomicReference<Object> capturedRef = new AtomicReference(Transmitter.capture());
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
typescript
@NonNull
public static Object capture() {
HashMap<Transmittee<Object, Object>, Object> transmittee2Value = new HashMap(transmitteeSet.size());
for(Transmittee<Object, Object> transmittee : transmitteeSet) {
try {
// 获取当前线程的 TransmittableThreadLocal 值
transmittee2Value.put(transmittee, transmittee.capture());
} catch (Throwable t) {
if (TransmittableThreadLocal.logger.isLoggable(Level.WARNING)) {
TransmittableThreadLocal.logger.log(Level.WARNING, "exception when Transmitter.capture for transmittee " + transmittee + "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
}
}
}
return new Snapshot(transmittee2Value);
}
capture()
方法的逻辑
typescript
@NonNull
public static Object capture() {
HashMap<Transmittee<Object, Object>, Object> transmittee2Value = new HashMap(transmitteeSet.size());
for (Transmittee<Object, Object> transmittee : transmitteeSet) {
try {
transmittee2Value.put(transmittee, transmittee.capture());
} catch (Throwable t) {
if (TransmittableThreadLocal.logger.isLoggable(Level.WARNING)) {
TransmittableThreadLocal.logger.log(Level.WARNING, "exception when Transmitter.capture for transmittee " + transmittee + "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
}
}
}
return new Snapshot(transmittee2Value);
}
- 创建
transmittee2Value
创建一个HashMap
,用于存储每个Transmittee
对象及其对应的值。 - 遍历
transmitteeSet
遍历transmitteeSet
中的每个Transmittee
对象,调用其capture()
方法捕获当前线程的值,并将结果存储到transmittee2Value
中。 - 捕获值
调用transmittee.capture()
方法捕获当前线程的值。如果捕获过程中发生异常,会记录警告日志并忽略该Transmittee
。 - 返回
Snapshot
将捕获的值封装成一个Snapshot
对象并返回。