1. 传统的ThreadLocal
ThreadLocal是线程本地变量。即每一个线程中都会存在一个ThreadLocal
对象。
ThreadLocal.set()方法:获取当前线程中的ThreadLocalMap对象,将ThreadLocal对象作为key,set()的值作为value,放入到线程中ThreadLocalMap中。
问题:若是在线程中开启子线程,那么子线程也会存在一个ThreadLocalMap对象,但是它不存在父线程ThreadLocalMap对象中的值。
@Slf4j
public class testThreadLocal {
@Test
public void testThreadLocal2() throws InterruptedException {
ThreadLocal<String> local = new ThreadLocal<>();
try {
local.set("我是主线程");
ExecutorService executorService = Executors.newFixedThreadPool(1);
CountDownLatch c1 = new CountDownLatch(1);
CountDownLatch c2 = new CountDownLatch(1);
executorService.execute(() -> {
System.out.println("线程1" + local.get());
c1.countDown();
});
c1.await();
executorService.execute(() -> {
System.out.println("线程2" + local.get());
c2.countDown();
});
c2.await();
executorService.shutdownNow();
} finally {
//使用完毕,清除线程中ThreadLocalMap中的key。
local.remove();
}
}
}
执行结果:
线程1null
线程2null
2. 可继承的InheritableThreadLocal
image.pngInheritableThreadLocal是ThreadLocal的子类,字面意义上是子线程可以继承父线程中的值。
案例1:
@Slf4j
public class testThreadLocal {
@Test
public void testInheritableThreadLocal() throws InterruptedException {
ThreadLocal<String> local = new InheritableThreadLocal<>();
local.set("我是主线程");
new Thread(() -> {
System.out.println("子线程1" + local.get());
}).start();
Thread.sleep(2000);
}
}
执行结果:
子线程1我是主线程
可以看到,子线程ThreadLocalMap也持有父线程ThreadLocalMap的值。
源码简介:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
...
/**
* 创建Map的时候,会初始化当前线程中的inheritableThreadLocals变量。
*
* @param t 当前的线程。
* @param firstValue 初始化table的第一个值。
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
public
class Thread implements Runnable {
/*
* 与此线程相关的InheritableThreadLocal值。这map是由InheritableThreadLocal类维护。
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//线程初始化
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
//如果inheritThreadLocals为true且父类存在inheritableThreadLocals,那么
//子类将继承父类的inheritableThreadLocals的值。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
}
由源码可知,线程只有在初始化的时候才会继承父类ThreadLocal的值。也就是若是线程池的话,以及子类依旧不能继承父类的ThreadLocal的值。
实验:
@Slf4j
public class testThreadLocal {
@Test
public void testThreadLocal2() throws InterruptedException {
ThreadLocal<String> local = new InheritableThreadLocal<>();
try {
local.set("我是主线程");
ExecutorService executorService = Executors.newFixedThreadPool(1);
CountDownLatch c1 = new CountDownLatch(1);
CountDownLatch c2 = new CountDownLatch(1);
//初始化init的时候,赋予了父线程的ThreadLocal的值
executorService.execute(() -> {
System.out.println("线程1" + local.get());
c1.countDown();
});
c1.await();
//主线程修改值
local.set("修改主线程");
//再次调用,查看效果
executorService.execute(() -> {
System.out.println("线程2" + local.get());
c2.countDown();
});
c2.await();
executorService.shutdownNow();
} finally {
//使用完毕,清除线程中ThreadLocalMap中的key。
local.remove();
}
}
}
执行结果:
线程1我是主线程
线程2我是主线程
可以看到,线程池只会初始化一次,依旧存在问题。
3. 最终解决方案TransmittableThreadLocal
阿里巴巴开源解决方案:https://github.com/alibaba/transmittable-thread-local
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.5</version>
<scope>compile</scope>
</dependency>
3.1 业务侵入式
alibaba提供了一系列的包装类,可以对ExecutorServic及其子类进行装饰,由TtlExecutors
类实现。
@Test
public void testInheritableThreadLocal3() throws InterruptedException {
TransmittableThreadLocal<String> local = new TransmittableThreadLocal<>();
local.set("我是主线程");
//生成额外的代理
ExecutorService executorService = Executors.newFixedThreadPool(1);
//核心装饰代码
executorService = TtlExecutors.getTtlExecutorService(executorService);
CountDownLatch c1 = new CountDownLatch(1);
CountDownLatch c2 = new CountDownLatch(1);
executorService.submit(() -> {
System.out.println("我是线程1:" + local.get());
c1.countDown();
});
c1.await();
local.set("修改主线程");
System.out.println(local.get());
executorService.submit(() -> {
System.out.println("我是线程2:" + local.get());
c2.countDown();
});
c2.await();
}
3.2 agent实现
需配置启动参数-javaagent:path/to/transmittable-thread-local-2.x.x.jar
@Test
public void testTTL() throws InterruptedException {
TransmittableThreadLocal<String> local = new TransmittableThreadLocal<>();
local.set("我是主线程");
//生成额外的代理
CountDownLatch c1 = new CountDownLatch(1);
CountDownLatch c2 = new CountDownLatch(1);
CompletableFuture.supplyAsync(() -> {
System.out.println("开启了线程1:" + local.get());
c1.countDown();
return "线程1的值";
});
c1.await();
local.set("修改主线程");
System.out.println(local.get());
CompletableFuture.supplyAsync(() -> {
System.out.println("开启了线程2:" + local.get());
c1.countDown();
return "线程2的值";
});
c2.await();
System.out.println("我是主线程:" + local.get());
}
执行结果:
开启了线程1:我是主线程
修改主线程
开启了线程2:修改主线程
idea修改配置.png
在ThreadLocal的需求场景即是TransmittableThreadLocal的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal』则是TransmittableThreadLocal目标场景。
下面是几个典型场景例子。
分布式跟踪系统 或 全链路压测(即链路打标)
日志收集记录系统上下文
Session级Cache
应用容器或上层框架跨应用代码给下层SDK传递信息
JAVA并发(4)— ThreadLocal源码角度分析是否真正能造成内存溢出!
网友评论