ThreadLocal
ThreadLocal解决的是每个线程可以拥有自己线程的变量实例。可以从隔离的角度解决变量线程安全,相信大家也十分熟悉,此处不再演示。
但是它并不支持子线程,因为父线程与子线程并不是同一个Thread,例:
public class UserContext {
private static ThreadLocal<String> userHolder = new ThreadLocal<>();
public static String getUser() {
return userHolder.get();
}
public static void setUser(String user) {
userHolder.set(user);
}
public static void clean() {
userHolder.remove();
}
}
public class Test {
@SneakyThrows
public static void main(String[] args) {
UserContext.setUser("小明");
System.out.println("父线程获取:" + UserContext.getUser());
new Thread(() -> {
// 无法获取父线程的ThreadLocal
System.out.println("子线程获取:" + UserContext.getUser());
}).start();
Thread.sleep(1000);
}
}
JDK为此提供另一个类解决此问题 ↓
InheritableThreadLocal
我们只需将上面的演示代码 new ThreadLocal<>() 替换成 new InheritableThreadLocal<>() 即可解决问题。
原理:
点开InheritableThreadLocal,实没多少代码,原理的码在此,java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, boolean)
init方法末尾初:
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
可以看到inheritableThreadLocals还是一个ThreadLocalMap,只不过是在Thread的init方法中把父Thread的inheritableThreadLocals变量copy了一份给自己。同样借助ThreadLocalMap子线程可以获取到父线程的所有变量。
根据它的实现,我们也可以看到它的缺点,就是Thread的init方法是在线程构造方法中copy的;
也就是说inheritThreadLocals 一旦创建就会copy父线程的信息,但是在线程池这种复用线程的场景下,线程被多次复用,而inheritThreadLocals还是原来的,这样就会有问题了。如下
public class Test {
@SneakyThrows
public static void main(String[] args) {
ExecutorService ttlExecutorService = Executors.newFixedThreadPool(1);
UserContext.setUser("小明");
ttlExecutorService.submit(() -> {
System.out.println("第一次,子线程获取:" + UserContext.getUser());
});
UserContext.setUser("小红");
ttlExecutorService.submit(() -> {
//此处由于是复用线程,所以获得的用户依然是小明
System.out.println("第二次,子线程获取:" + UserContext.getUser());
});
Thread.sleep(1000);
}
}
为此阿里开源了一个工具 TransmittableThreadLocal
TransmittableThreadLocal
使用例子:
public class UserContext {
private static ThreadLocal<String> userHolder = new TransmittableThreadLocal<>();
public static String getUser() {
return userHolder.get();
}
public static void setUser(String user) {
userHolder.set(user);
}
public static void clean() {
userHolder.remove();
}
}
public class Test {
@SneakyThrows
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
//需要手动修饰线程池
//如果仅仅是吧InheritableThreadLocal修改为TransmittableThreadLocal是不起作用的。
executorService = TtlExecutors.getTtlExecutorService(executorService);
UserContext.setUser("小明");
executorService.submit(() -> {
System.out.println("第一次,子线程获取:" + UserContext.getUser());
});
UserContext.setUser("小红");
executorService.submit(() -> {
//不再是小明,而是小红
System.out.println("第二次,子线程获取:" + UserContext.getUser());
});
executorService.shutdown();
Thread.sleep(1000);
}
}
原理:在线程run方法之前复制了父线程的ThreadLocal变量
先看修饰线程池的代码做了啥
@Nullable
public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
return (ExecutorService)(!TtlAgent.isTtlAgentLoaded() && executorService != null && !(executorService instanceof TtlEnhanced) ? new ExecutorServiceTtlWrapper(executorService, true) : executorService);
}
主要看ExecutorServiceTtlWrapper里面的内容
提交任务时,使用TtlRunnable创建方法
@NonNull
@Override
public <T> Future<T> submit(@NonNull Runnable task, T result) {
return executorService.submit(TtlRunnable.get(task, false, idempotent), result);
}
TtlRunnable 在run方法之前复制了父线程的ThreadLocal变量
com.alibaba.ttl.TtlRunnable#run
@Override
public void run() {
//当前线程ThreadLocal
final Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
//父线程ThreadLocal
final Object backup = replay(captured);
try {
runnable.run();
} finally {
restore(backup);
}
}
它还提供利用Java Agent,无需修饰线程池的无入侵用法
请查阅 TransmittableThreadLocal