阅读本文前,需要了解
① ThreadLocal的特性,参考:Java随笔1-ThreadLocal
② InheritableThreadLocal的特性,参考:Java随笔2-InheritableThreadLocal
③ transmittable:adj. 可传送的,可传播的
④ TransmittableThreadLocal 是Alibaba开源的,需配合TtlRunnable 和 TtlCallable 使用
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.5.1</version>
</dependency>
1、特性
InheritableThreadLocal解决了父子线程之间变量传递的问题,为什么InheritableThreadLocal能解决,随笔2中分析了是new Thread时,子线程拷贝了父线程的inheritableThreadLocals属性。
多线程场景下,总是会创建新线程吗?答案是no!因为线程池,先把线程创建完,随用随取。
基于线程池的多线程执行任务,根据原理似乎InheritableThreadLocal不能保证父子线程之间变量的正确传递,是吗?那TransmittableThreadLocal呢?且看以下demo:
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import org.junit.Test;
import java.util.concurrent.*;
public class TransmittableTLTest {
InheritableThreadLocal<String> inheritableTL = new InheritableThreadLocal<>();
TransmittableThreadLocal<String> transmittableTL = new TransmittableThreadLocal<>();
ExecutorService pool = new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
@Test
public void compare() throws InterruptedException {
inheritableTL.set("祖先");
CountDownLatch latch1 = new CountDownLatch(10);
for (int i = 1; i <= 10; i++) {
int index = i;
pool.submit(() -> {
System.out.println("inheritableTL, 儿子线程" + Thread.currentThread().getName() + "取出:" + inheritableTL.get());
inheritableTL.set("儿子" + index);
latch1.countDown();
});
}
latch1.await();
System.out.println("inheritableTL, 主线程取出:" + inheritableTL.get() + "\n");
transmittableTL.set("祖先");
CountDownLatch latch2 = new CountDownLatch(10);
for (int i = 1; i <= 10; i++) {
int index = i;
Runnable runnable = TtlRunnable.get(() -> {
System.out.println("transmittableTL, " + index + " 儿子线程取出:" + transmittableTL.get());
transmittableTL.set("儿子" + index);
latch2.countDown();
});
pool.submit(runnable);
}
latch2.await();
System.out.println("transmittableTL, 主线程取出:" + transmittableTL.get());
}
}
执行结果:
inheritableTL, 儿子线程pool-1-thread-1取出:祖先
inheritableTL, 儿子线程pool-1-thread-2取出:祖先
inheritableTL, 儿子线程pool-1-thread-3取出:祖先
inheritableTL, 儿子线程pool-1-thread-4取出:祖先
inheritableTL, 儿子线程pool-1-thread-5取出:祖先
inheritableTL, 儿子线程pool-1-thread-5取出:儿子5
inheritableTL, 儿子线程pool-1-thread-2取出:儿子2
inheritableTL, 儿子线程pool-1-thread-1取出:儿子1
inheritableTL, 儿子线程pool-1-thread-3取出:儿子3
inheritableTL, 儿子线程pool-1-thread-5取出:儿子6
inheritableTL, 主线程取出:祖先
transmittableTL, 3 儿子线程取出:祖先
transmittableTL, 5 儿子线程取出:祖先
transmittableTL, 1 儿子线程取出:祖先
transmittableTL, 6 儿子线程取出:祖先
transmittableTL, 4 儿子线程取出:祖先
transmittableTL, 2 儿子线程取出:祖先
transmittableTL, 10 儿子线程取出:祖先
transmittableTL, 9 儿子线程取出:祖先
transmittableTL, 8 儿子线程取出:祖先
transmittableTL, 7 儿子线程取出:祖先
transmittableTL, 主线程取出:祖先
注意,线程池初始设定了coreSize = maxSize = 5。
不出所料,inheritablTL在前5次,还能取出主线程设定的“祖先”,后面开始乱套了,取到的是该线程上一次执行时设定的“儿子x”信息。
不负所望,transmittableTL在10次执行中取出的一直是主线程设定的信息。
总结:TransmittableThreadLocal是用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。
2、原理
InheritableThreadLocal解决父子线程间变量传递,主要是new Thread时的拷贝;类似,TransmittableThreadLocal解决父线程与线程池线程间变量传递,主要是创建Runnable时,捕获当时的主线程环境,在执行任务时,把环境回放到当前线程池线程上;任务执行完,恢复环境。一般的Runnable显然是不支持的,所以为了配合TransmittableThreadLocal,需要使用对应的TtlRunnable。
以demo为例,下图描述了线程池中某一线程初次执行的流程: