前面讲解了增强版的ThreadLocal-TransmittableThreadLocal可以优雅解决线程变量的继承问题,本节我们来探讨其实现。
简单回顾如何使用TransmittableThreadLocal解决线程变量继承问题:
// 0.创建线程池
private static final ThreadPoolExecutor bizPoolExecutor = new ThreadPoolExecutor(2, 2, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(1));
public static void main(String[] args) throws InterruptedException {
// 1 创建线程变量
TransmittableThreadLocal parent = new TransmittableThreadLocal();
// 2 投递三个任务
for (int i = 0; i < 3; ++i) {
bizPoolExecutor.execute(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 3休眠4s
Thread.sleep(4000);
// 4.设置线程变量
parent.set("value-set-in-parent");
// 5. 提交任务到线程池
Runnable task = () -> {
try {
// 5.1访问线程变量
System.out.println("parent:" + parent.get());
} catch (Exception e) {
e.printStackTrace();
}
};
// 5.2额外的处理,生成修饰了的对象ttlRunnable
Runnable ttlRunnable = TtlRunnable.get(task);
//5.3
bizPoolExecutor.execute(ttlRunnable);
}
介于InheritableThreadLocal已经实现了new Thread情况下的线程变量继承问题,所以TransmittableThreadLocal可以直接继承它,来继承该功能。其他我们需要做的就是如何在任务提交到线程池后,线程池线程运行任务时候任务内可以从线程变量里面获取到父线程设置的值。
InheritableThreadLocal的实现是在new Thread时候把父线程中的inheritableThreadLocals变量复制到了子线程,从而实现线程变量的继承特性。而现在情况下很明显我们需要在提交任务到线程池前,把父线程中的线程变量保存到任务内,然后等线程池内线程执行任务前把保存的父线程的线程变量复制到线程池中的执行线程上,然后运行我们的任务,等任务运行完毕后,在清除掉执行线程上的线程变量。
可知第一我们需要包装提交到线程池内的任务,里面添加一个变量来保存父线程的线程变量。没错,TransmittableThreadLocal就是这样的思路,只是限于包权限限制,其内部做了一层缓存holder以便获取线程中保存的线程变量。
如上代码5.2就是把我们的任务使用TtlRunnable.get(task);包装了下,其内部就是拷贝父线程中的线程变量到包装的任务内保存起来:
public static TtlRunnable get(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
return get(runnable, releaseTtlValueReferenceAfterRun, false);
}
public static TtlRunnable get(Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
....
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference
public static Object capture() {
Map, Object> captured = new HashMap, Object>();
for (TransmittableThreadLocal> threadLocal : holder.get().keySet()) {
captured.put(threadLocal, threadLocal.copyValue());
}
return captured;
}
可知 capture()方法就是把父线程内的线程变量获取过来,最后保存到了包装任务TtlRunnable内的capturedRef,另外TtlRunnable内runnable是我们实际要提交的任务。一个问题,holder中的threadLocal什么时候塞进入的,这个就是在代码4的时候:
public final void set(T value) {
super.set(value);
if (null == value) { // may set null to remove value
removeValue();
} else {
addValue();
}
}
private void addValue() {
if (!holder.get().containsKey(this)) {
holder.get().put(this, null); // WeakHashMap supports null value.
}
}
可知holder里面key主要保存了当前的TransmittableThreadLocal变量的,引用。
最后我们看TtlRunnable的run方法:
public void run() {
Object captured = capturedRef.get();//A
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
Object backup = replay(captured);//B
try {
runnable.run();//C
} finally {
restore(backup);//D
}
}
代码A获取我们保存的父线程里面的线程变量,代码B设置到当前线程里面,代码C执行我们提交的任务,代码D恢复执行线程上下文。
本文概要介绍了其实现原理,详细大家可以看源码自由体会。
谈谈Golang并发编程
如何动态获取Dubbo服务提供方地址列表
使用指定IP调用Dubbo服务
高性能可扩展分布式RPC框架Dubbo-内核原理揭秘
Java并发编程视频分享-第一期
Java并发编程视频分享-第二期
Java 并发编程之美
Reactive(响应式)编程-Reactor
Java 异步编程导论
谈谈Netty的线程模型
增强版的ThreadLocal-TransmittableThreadLocal
——图书推荐——
——关注本号——