TransmittableThreadLocal实现父线程值传递变更值给线程池子线程
InheritableThreadLocal只会在线程池中的线程初次创建的时候,从父线程拷贝属性,而父线程再次修改这个属性时,线程池中的这个线程是无法再次感知到的。
我们在日常的开发中,很少主动new线程,都是使用线程池,而线程池中,不会频繁的创建线程,更多的场景是线程创建一次,重复使用。
如果线程中的属性在上次使用后被修改,下次使用后,子线程中保存的属性值依然是首次使用时的值
TransmittableThreadLocal实现父线程值传递变更值给线程池子线程
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author : mazhen
* Date : 2021/11/12
* Time : 4:11 下午
* ---------------------------------------
* Desc :
*/
public class ITL {
public static TransmittableThreadLocal<String> param = new TransmittableThreadLocal<>();
public static void main(String[] args) {
param.set("main");
ExecutorService pool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
pool.submit(() -> {
System.out.println(param.get());
});
param.set("main2");
pool.submit(() -> {
System.out.println(param.get());
});
pool.submit(() -> {
System.out.println(param.get());
});
pool.shutdown();
}
}
----------------------
main
main2
main2
可以看到,使用TransmittableThreadLocal并配合TtlExecutors.getTtlExecutorService()可以让子线程感知到父线程对共享变量的实时变化。
子线程对父线程值的修改只会影响当前运行时子线程,不影响父线程和其他子线程,也不影响下次运行
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author : mazhen
* Date : 2021/11/12
* Time : 4:11 下午
* ---------------------------------------
* Desc :
*/
public class ITL {
public static TransmittableThreadLocal<String> param = new TransmittableThreadLocal<>();
public static void main(String[] args) {
param.set("main");
ExecutorService pool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
pool.submit(() -> {
System.out.println(param.get());
});
param.set("main2");
pool.submit(() -> {
param.set("child value only use in this");
System.out.println(param.get());
});
pool.submit(() -> {
System.out.println(param.get());
});
pool.shutdown();
}
}
--------------
main
child value only use in this
main2
TransmittableThreadLocal实现原理
与ThreadLocal保存在Thread中不同,holder就保存在TransmittableThreadLocal中:
//holder就保存在TransmittableThreadLocal
private 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) {
// InheritableThreadLocal.set(),这里保留了父类的特性,即会把值保存到ThreadLocal
super.set(value);
if (null == value) {
this.removeValue();
} else {
this.addValue();
}
}
private void addValue() {
if (!((Map)holder.get()).containsKey(this)) {
((Map)holder.get()).put(this, (Object)null);
}
}
set时依然会像thread本身特点一样设置ThreadLocal,但是更重要的是TTL自己也创建了一个holder去保存值,而holder就保存在TransmittableThreadLocal中
TtlExecutors对普通的ExecutorService进行了封装
//封装ExecutorService
public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
return (ExecutorService)(!TtlAgent.isTtlAgentLoaded() && executorService != null && !(executorService instanceof TtlEnhanced) ? new ExecutorServiceTtlWrapper(executorService) : executorService);
}
///ExecutorServiceTtlWrapper的实现
public <T> Future<T> submit(@Nonnull Runnable task, T result) {
return this.executorService.submit(TtlRunnable.get(task), result);
}
//核心就是TtlRunnable
public static TtlRunnable get(@Nullable Runnable runnable) {
return get(runnable, false, false);
}
public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
if (null == runnable) {
return null;
} else if (runnable instanceof TtlEnhanced) {
if (idempotent) {
return (TtlRunnable)runnable;
} else {
throw new IllegalStateException("Already TtlRunnable!");
}
} else {
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
}
//TtlRunnable实现了runable,可以看到运行业务前后它会做子线程前后的现场备份和恢复工作
@Override
public void run() {
/**
* capturedRef是主线程传递下来的ThreadLocal的值。
*/
Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
/**
* 1. backup(备份)是子线程已经存在的ThreadLocal变量;
* 2. 将captured的ThreadLocal值在子线程中set进去;
*/
Object backup = replay(captured);
try {
/**
* 待执行的线程方法;
*/
runnable.run();
} finally {
/**
* 在子线程任务中,ThreadLocal可能发生变化,该步骤的目的是
* 回滚{@code runnable.run()}进入前的ThreadLocal的线程
*/
restore(backup);
}
}
/**
* 将快照重做到执行线程
* @param captured 快照
*/
public static Object replay(Object captured) {
// 获取父线程ThreadLocal快照
final Snapshot capturedSnapshot = (Snapshot) captured;
return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
}
/*****************************************************
* 重放TransmittableThreadLocal,并保存执行线程的原值
****************************************************/
private static WeakHashMap<TransmittableThreadLocalCode<Object>, Object> replayTtlValues(WeakHashMap<TransmittableThreadLocalCode<Object>, Object> captured) {
WeakHashMap<TransmittableThreadLocalCode<Object>, Object> backup = new WeakHashMap<TransmittableThreadLocalCode<Object>, Object>();
for (final Iterator<TransmittableThreadLocalCode<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
TransmittableThreadLocalCode<Object> threadLocal = iterator.next();
// backup
// 遍历 holder,从 父线程继承过来的,或者之前注册进来的
backup.put(threadLocal, threadLocal.get());
// clear the TTL values that is not in captured
// avoid the extra TTL values after replay when run task
// 清除本次没有传递过来的 ThreadLocal,和对应值
// -- 第一点:可能会有因为 InheritableThreadLocal 而传递并保留的值
// -- 第二点:保证主线程set过的ThreadLocal不被传递过来。明确其传递是由业务代码控制,就是明确 set 过值的
if (!captured.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
// set TTL values to captured
// 将 map 中的值,设置到快照
// 内部调用了 beforeExecute 和 afterExecute 方法。默认不做任何处理
setTtlValuesTo(captured);
// call beforeExecute callback
// TransmittableThreadLocal 的回调方法,在任务执行前执行
doExecuteCallback(true);
return backup;
}
private static WeakHashMap<ThreadLocal<Object>, Object> replayThreadLocalValues( WeakHashMap<ThreadLocal<Object>, Object> captured) {
final WeakHashMap<ThreadLocal<Object>, Object> backup = new WeakHashMap<ThreadLocal<Object>, Object>();
for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) {
final ThreadLocal<Object> threadLocal = entry.getKey();
backup.put(threadLocal, threadLocal.get());
final Object value = entry.getValue();
// 如果值是标记已删除,则清除
if (value == threadLocalClearMark) threadLocal.remove();
else threadLocal.set(value);
}
return backup;
}
/*********************************************
* 恢复备份的原快照
*********************************************/
public static void restore( Object backup) {
// 将之前保存的TTL和threadLocal原来的数据覆盖回去
final Snapshot backupSnapshot = (Snapshot) backup;
restoreTtlValues(backupSnapshot.ttl2Value);
restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
}
private static void restoreTtlValues( WeakHashMap<TransmittableThreadLocalCode<Object>, Object> backup) {
// call afterExecute callback
// 调用执行完后回调接口
doExecuteCallback(false);
// 移除子线程新增的TTL
for (final Iterator<TransmittableThreadLocalCode<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
TransmittableThreadLocalCode<Object> threadLocal = iterator.next();
// 恢复快照时,清除本次传递注册进来,但是原先不存在的 TransmittableThreadLocal
// 移除掉所有不在备份里面的TTL数据,应该是为了避免内存泄漏吧
// clear the TTL values that is not in backup
// avoid the extra TTL values after restore
if (!backup.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
// 重置为原来的数据(就是恢复回备份前的值)
// restore TTL values
setTtlValuesTo(backup);
}
private static void setTtlValuesTo( WeakHashMap<TransmittableThreadLocalCode<Object>, Object> ttlValues) {
for (Map.Entry<TransmittableThreadLocalCode<Object>, Object> entry : ttlValues.entrySet()) {
TransmittableThreadLocalCode<Object> threadLocal = entry.getKey();
// set 的同时,也就将 TransmittableThreadLocal 注册到当前线程的注册表了
threadLocal.set(entry.getValue());
}
}
步骤:
1装饰Runnable,将主线程的TTL传入到TtlRunnable的构造方法中
2将子线程的TTL的值进行备份,将主线程的TTL设置到子线程中(value是对象引用,可能存在线程安全问题);
3执行子线程逻辑
4删除子线程新增的TTL,将备份还原重新设置到子线程的TTL中
其他注意点
TTL为什么不直接继承ThreadLocal?
因为有些业务需要用到ITL特性,如果直接继承ThreadLocal,就会丢失ITL的父拷贝到子线程数据的特性(子线程创建时拷贝)
为什么需要在run执行完之后调用restore()?
restore里面会主动调用remove()回收,避免内存泄露(会删除子线程新增的TTL)
不调用restore()的话,就会覆盖之前backup备份部分子线程的数据,这样可能在业务上有隐患
TTL存在线程安全问题?
存在的,因为默认都是引用类型拷贝,如果子线程修改了数据,主线程是可以感知到的
TTL是否存在内存泄露问题?
TTL维护的holder本身是一个static来的,使用的时候会调用restore(),然后里面显式调用remove()清楚子线程新增TTL,所以正确使用下是没有内存泄露问题的
参考原文链接:https://blog.csdn.net/qq_28666081/article/details/119834058