这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党
线程、父子线程、线程池
我们知道线程之间的值传递使用JDK自带的ThreadLocal即可解决,如果遇到需要父子线程值传递的场景也可以使用JDK提供的InheritableThreadLocal,但更多的业务场景实际是需要把任务提交给线程池时的ThreadLocal值传递到任务执行时
关于InheritableThreadLocal详细介绍说明请参考之前博文 weihubeats.blog.csdn.net/article/det…
InheritableThreadLocal简单测试
上面说的概念有点难懂,我们简单看看使用InheritableThreadLocal + 线程池的一个demo会有什么问题吧
public class InheritableThreadLocalTest {
private static final AtomicInteger ID_SEQ = new AtomicInteger();
private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(1, r -> new Thread(r, "thread-" + ID_SEQ.getAndIncrement()));
private static Integer i = 0;
private static final ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws Exception {
// 模拟接口调用
IntStream.range(0, 10).forEach(InheritableThreadLocalTest::testInheritableThreadLocal);
THREAD_POOL.shutdown();
}
public static void testInheritableThreadLocal(int s) {
inheritableThreadLocal.set("小奏技术" + i++);
Future<?> submit = THREAD_POOL.submit(new ZouTask("任务" + s));
try {
submit.get();
} catch (Exception e) {
e.printStackTrace();
}
}
public static class ZouTask implements Runnable{
String taskName;
public ZouTask(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(taskName + "线程:" + Thread.currentThread().getName() + "获取到的值: " + inheritableThreadLocal.get());
inheritableThreadLocal.remove();
}
}
运行结果:
可以看到我们在获取到第一次的值后就再也获取不到后面的InheritableThreadLocal的值了。正确的运行结果应该是如下的:
为什么会出现这种错误呢?原因很简单,
InheritableThreadLocal只有在创建子线程的时候才会传递值。而线程池由于线程创建后会一直复用,所以导致后面获取不到值
TransmittableThreadLocal介绍
这里我们要实现线程池上线文的传递(值)就不得不引出我们今天的主角TransmittableThreadLocal,TransmittableThreadLocal是阿里开源的线程池异步上线文传递的一个组件,并不是JDK自带的
功能介绍(官网介绍):
TransmittableThreadLocal(TTL):在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。一个Java标准库本应为框架/中间件设施开发提供的标配能力,本库功能聚焦 & 0依赖,支持Java 17/16/15/14/13/12/11/10/9/8/7/6。 JDK的
InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal值传递到 任务执行时 整个TransmittableThreadLocal库的核心功能(用户API与框架/中间件的集成API、线程池ExecutorService/ForkJoinPool/TimerTask及其线程工厂的Wrapper),只有 ~1000 SLOC代码行,非常精小
TransmittableThreadLocal使用
引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.4</version>
</dependency>
我们还是基于上面那个例子来说明
public class TransmittableThreadLocalTest {
private static final AtomicInteger ID_SEQ = new AtomicInteger();
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1, r -> new Thread(r, "thread-" + ID_SEQ.getAndIncrement()));
private static final TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();
private static Integer i = 0;
public static void main(String[] args) throws Exception {
// 模拟接口调用
IntStream.range(0, 10).forEach(TransmittableThreadLocalTest::testInheritableThreadLocal);
EXECUTOR.shutdown();
}
public static void testInheritableThreadLocal(int s) {
ttl.set("小奏技术" + i++);
Future<?> submit = EXECUTOR.submit(TtlRunnable.get(new ZouTask("任务" + s)));
try {
submit.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public static class ZouTask implements Runnable{
String taskName;
public ZouTask(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(taskName + "线程:" + Thread.currentThread().getName() + "获取到的值: " + ttl.get());
ttl.remove();
}
}
}
这里面有几个细节:
- 用
TransmittableThreadLocal替换InheritableThreadLocal - 用
TtlRunnable增强JDK原始的Runnable接口
TransmittableThreadLocal使用方式大致如下。一些高级用法可能比如无侵入Agent接入等
TransmittableThreadLocal 在开源项目中的使用
TransmittableThreadLocal目前已经在很多开源项目中都有使用
感兴趣可以自己去看看
总结
TransmittableThreadLocal的使用大致就到这里。源码和原理相关的部分感兴趣可以去研究下,官网也有一些说明,这里就暂时不讲解了