【多线程开发 2】从代码到实战TransmittableThreadLocal
本文将从以下几个点讲解TransmittableThreadLocal(为了方便写以下简称ttl):
-
前身
-
是什么?
-
可以用来做什么?
-
源码原理
-
实战
前身
ThreadLocal
要了解ttl就要先了解Java自带的类ThreadLocal,threadlocal是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,,用于存储一些线程不安全的公共变量,通过“给每一个线程一个线程不安全的变量的拷贝”,来达到线程安全的目的,就不会出现变量多个线程之间共享的问题。
ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
InheritableThreadLocal
使用ThreadLocal可以解决线程安全问题,但是也有一定的局限性,比如无法在父子线程之间传递信息,因此InheritableThreadLocal就是JDK为了解决这个问题而创建的
TTL是什么?可以做什么?
在现在开发的情况下肯定是需要复用线程的,如果说InheritableThreadLocal在生成子进程的时候会做信息传递,但是在使用线程池或者其他需要复用线程的地方,由于会不产生新的Thread,而是直接使用空闲的已建成的Thread,所以的话InheritableThreadLocal有时候会不能用,此时可以通过使用TTL解决对应问题。
读取线程间传递的ThreadLocal 值比较麻烦,ThreadLocal 和 InheritableThreadLocal 都没有开放内部的 ThreadLocalMap,不能直接读取。所以TTL继承了InheritableThreadLocal,在每次调用 ThreadLocal的 set/get/remove 等接口的时候,为 Thread 记录到底绑定了哪些需要发生线程间传递的 ThreadLocal 对象。
在创建runnable的时候,TTL会通过holder遍历全部的TTLRunnable快照,看出上下文中有哪些线程上的信息需要进行复制。
TTL的GitHub项目地址:https://github.com/alibaba/transmittable-thread-local,感兴趣的话可以查看一下源码,后续有时间的话我会出一篇详细解析TTL源码的项目地址。
实战
其中需要使用拦截器辅助实现,需要读者依据相应技术架构自行实现
使用TTL实现MDC日志在多系统之间的信息传输
public class CustomMdcAdapters implements MDCAdapter {
private static final int WRITE_OPERATION = 1;
private static final int MAP_COPY_OPERATION = 2;
private static CustomMdcAdapters mtcMDCAdapter;
static {
mtcMDCAdapter = new CustomMdcAdapters();
MDC.mdcAdapter = mtcMDCAdapter;
}
private final ThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new TransmittableThreadLocal<>();
private final ThreadLocal<Integer> lastOperation = new ThreadLocal<>();
public static MDCAdapter getInstance() {
return mtcMDCAdapter;
}
private static boolean wasLastOpReadOrNull(Integer lastOp) {
return lastOp == null || lastOp == MAP_COPY_OPERATION;
}
private Integer getAndSetLastOperation(int op) {
Integer lastOp = lastOperation.get();
lastOperation.set(op);
return lastOp;
}
private Map<String, String