- 快速入手
- 源码设计解读
2.1 TransmittableThreadLocal的原理
2.2 holder变量的设计
2.3 TTL为什么不继承ThreadLocal
2.4 为什么会有restore步骤 - 使用事项
3.1 是否存在线程安全问题
-3.1.1 子线程修改TTL,父线程能否感应到TTL变化?
-3.1.2 父线程修改TTL,子线程能否感应到TTL变化?
-3.1.3 线程不安全的解决方案
3.2 是否存在内存泄漏问题
3.3 子线程中需要调用remove方法吗?
3.4 主线程执行remove方法会清空子线程的TTL的值吗?
3.5 如何创建出默认值的TTL
3.6 SpringBoot启动时,调用TTL的get方法如何保证线程安全
3.7 创建出安全的TTL的方式 - 相关文章
1. 快速入手
github地址:https://github.com/alibaba/transmittable-thread-local
pom依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.5</version>
<scope>compile</scope>
</dependency>
业务使用:
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.tellme.po.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
@RestController
public class ThreadLocalController {
ExecutorService executorService =
TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
public static TransmittableThreadLocal<String> l2 = new TransmittableThreadLocal<>();
public static TransmittableThreadLocal<User> local = new TransmittableThreadLocal<>();
@RequestMapping("/local/t1")
public void t1() {
//第一次使用线程池时前,创建多个ThreadLocal的值。
local.set(new User("001", "sam"));
l2.set("李白不偷蓝");
executorService.execute(() -> {
log.info("【/local/t1的add操作】子线程打印数据{}", local.get());
});
executorService.execute(() -> {
log.info("【/local/t1的add操作】子线程打印数据{}", local.get());
});
local.remove();
l2.remove();
}
@RequestMapping("/local/t4")
public void t4() {
executorService.execute(() -> {
log.info("【/local/t4】子线程打印数据{}", l2.get());
});
}
}
2. 源码设计解读
2.1 TransmittableThreadLocal的原理
在设计模式上采用装饰器模式去增强Runnable等任务。在向线程池提交Runnable任务时,去装饰Runnable,将主线程的ThreadLocal通过构造方法传递到Runnable对象。然后在run()方法中设置进去。
注意点:TransmittableThreadLocal需要配套使用TtlExecutors
。
TtlRunnable源码分析
@Override
public void run() {
/**
* capturedRef是主线程传递下来的ThreadLocal的值。
*/
Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
/**
* 1. backup(备份)是子线程已经存在的ThreadLocal变量;
* 2. 将captured的ThreadLocal值在子线程中set进去;
*/
Object backup = replay(captured);
try {
/**
* 待执行的线程方法;
*/
runnable.run();
} finally {
/**
* 在子线程任务中,ThreadLocal可能发生变化,该步骤的目的是
* 回滚{@code runnable.run()}进入前的ThreadLocal的线程
*/
restore(backup);
}
}
即步骤总结为:
- 装饰Runnable,将主线程的TTL传入到TtlRunnable的构造方法中;
- 将子线程的TTL的值进行备份,将主线程的TTL设置到子线程中(value是对象引用,可能存在线程安全问题);
- 执行子线程逻辑;
- 删除子线程新增的TTL,将备份还原重新设置到子线程的TTL中;
详细解读见下文:
- 2.4 为什么会有restore步骤
- 3.1 是否存在线程安全问题
- 3.3 子线程中需要调用remove方法吗?
2.2 holder变量的设计
疑问:主线程需要将TransmittableThreadLocal传递到子线程,那么主线程如何记录所有的TransmittableThreadLocal?
Thread中存在下面两个变量,存储的是ThreadLocal的值和InheritableThreadLocal的值。并且ThreadLocal和InheritableThreadLocal分别实现了get()方法,可以获取到对应的ThreadLocalMap。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
- TransmittableThreadLocal继承ThreadLocal,那么便去threadLocals填充值;
- TransmittableThreadLocal继承InheritableThreadLocal,那么便去inheritableThreadLocals填充值;
TransmittableThreadLocal无论继承哪个类,若只是将TransmittableThreadLocal传递到子线程(粒度小),那么就不能简单的使用get()方法。
于是:TransmittableThreadLocal需要重新创建一个线程级别的缓存
,来记录某个线程所有的TransmittableThreadLocal对象。
线程级别的缓存——在TransmittableThreadLocal创建一个ThreadLocal。
源码位置:TransmittableThreadLocal
// Note about the holder:
// 1. holder self is a InheritableThreadLocal(a *ThreadLocal*).
// 2. The type of value in the holder is WeakHashMap<TransmittableThreadLocal<Object>, ?>.
// 2.1 but the WeakHashMap is used as a *Set*:
// - the value of WeakHashMap is *always null,
// - and be never used.
// 2.2 WeakHashMap support *null* value.
private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
}
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
}
};
这就是holder的由来:无法对Thread进行扩展,且只想将TransmittableThreadLocal传递到子线程。即自己去维护一个线程级别的全局缓存。
-
static
修饰:一个线程中,无论TransmittableThreadLocal被创建多少次,需要保证维护的是同一个缓存。即static保证; -
InheritableThreadLocal
:这个后续展开,其实和单纯使用ThreadLocal区别不大; -
WeakHashMap
:弱引用(发生GC便回收),这个是为了确保本框架不会造成潜在的内存泄漏;
2.3 TTL为什么不继承ThreadLocal
disable Inheritable when it's not necessary and buggy(eg. has potential memory leaking problem) #100
InheritableThreadLocal是一个潜在的泄漏问题。新创建的线程永远不知道如何处理继承的值。应在任务的生命周期内创建和清除可传输的线程本地值。InheritableThreadLocal 会将可传输线程本地值的管理泄漏到任务之外。
测试代码见:TransmittableThreadLocal会不会有内存泄漏的风险? #281
作者设计理念:
当然:TransmittableThreadLocal也是可以关闭Inheritable的。详见
- 对于线程池 TTL提供的DisableInheritable 支持
https://alibaba.github.io/transmittable-thread-local/apidocs/2.13.0-Beta1/com/alibaba/ttl/threadpool/DisableInheritableThreadFactory.html - disable Inheritable when it's not necessary and buggy(eg. has potential memory leaking problem) #100
2.4 为什么会有restore步骤
- Runnable可能由主线程执行,若Runnable修改了TransmittableThreadLocal,可能会造成主线程的TransmittableThreadLocal值变化,造成bug;
- restore中由框架进行了ttl的remove操作,无需开发人员在子线程中显式调用remove()方法,也不会造成内存泄漏。
代码位置:com.alibaba.ttl.TransmittableThreadLocal.Transmitter#restoreTtlValues
3. 使用事项
3.1 是否存在线程安全问题
异步线程中修改ThreadLocal值,主线程也会被修改?(DB Session传递场景) #253
TransmittableThreadLocal
有restore
的操作,但是子线程中修改了对象的引用,主线程是可以感知到的。可能存在线程安全问题。
3.1.1 子线程修改TTL,父线程能否感应到TTL变化?
默认是可以的,线程不安全问题的具体体现,解决方案详见
3.1.3 线程不安全的解决方案
。
3.1.2 父线程修改TTL,子线程能否感应到TTL变化?
默认是可以的,线程不安全问题的具体体现,解决方案详见
3.1.3 线程不安全的解决方案
。
3.1.3 线程不安全的解决方案
在restore方法中(详见
2.4 为什么会有restore步骤
),会remove掉子线程的TTL。但是在replay
方法父子线程传递的是对象引用。父子线程修改对象便可能会导致线程不安全问题。
public static TransmittableThreadLocal<Map<String, String>> l2 = new TransmittableThreadLocal<Map<String, String>>() {
/**
* 完成对象的copy,对于复杂的对象,可以采用序列化反序列化的方式进行深拷贝
*/
@Override
public Map<String, String> copy(Map<String, String> parentValue) {
return new HashMap<>(parentValue);
}
};
注意:深拷贝的问题。JAVA基础篇(3)-深克隆与浅克隆
3.2 是否存在内存泄漏问题
TransmittableThreadLocal会不会有内存泄漏的风险? #281
Inheritable
能力/功能 (即第一次创建Thread
时的上下文传递)引发的问题。
解决方法 参见 #279 (comment) : @yexuerui
TTL提供了 关闭
Inheritable
能力的解法:
(可能要理解一下 这个解法及其背景原因,有些复杂性。 <g-emoji class="g-emoji" alias="smile" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f604.png" style="box-sizing: border-box; font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1.25em; font-weight: 400; line-height: 1; vertical-align: -0.075em; font-style: normal !important;">😄</g-emoji> )
- 参见
TransmittableThreadLocal
主类的javadoc
说明:
https://alibaba.github.io/transmittable-thread-local/apidocs/2.13.0-Beta1/com/alibaba/ttl/TransmittableThreadLocal.html
- 对于线程池 TTL提供的DisableInheritable 支持
https://alibaba.github.io/transmittable-thread-local/apidocs/2.13.0-Beta1/com/alibaba/ttl/threadpool/DisableInheritableThreadFactory.html- disable Inheritable when it's not necessary and buggy(eg. has potential memory leaking problem) #100
这个Issue中解释了 为什么要做关闭Inheritable
能力的原因
3.3 子线程中需要调用remove方法吗?
不需要,结果详见
2.4 为什么会有restore步骤
3.4 主线程执行remove方法会清空子线程的TTL的值吗?
不会。
主线程调用remove方法,将TTL对应的value的引用指向null。
但是TTL传递到子线程,子线程也会持有一个value的引用。依旧可以获取到TTL的值。
3.5 如何创建出默认值的TTL
- 创建出的TTL的value默认值不为null,可以重写
initialValue
方法; - 因为TTL继承与ITL,也可以重写
childValue
方法,实现new Thread()时父线程不向子线程传递变量;当然官方推荐使用DisableInheritableThreadFactory去装饰我们线程池的ThreadFactory;
public static TransmittableThreadLocal<Map<String, String>> l2 = new TransmittableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<>();
}
//防止ITL的潜在的内存泄漏(官方也提供对应的API去实现)可以不重写方法。
protected Map<String, String> childValue(Map<String, String> parentValue) {
return initialValue();
}
};
3.6 SpringBoot启动时,调用TTL的get方法如何保证线程安全
https://github.com/alibaba/transmittable-thread-local/issues/282
因为TTL底层使用ITL,会导致在new线程的时候,父子线程的数据传递,且无法销毁。
背景:
- 项目启动的时候,存在TTL的get操作,于是main线程存在TTL的value;
- 当请求进入时,Tomcat线程池(不会被TtlExecutors装饰)会开启子线程来执行业务逻辑;
- main线程会将TTL(此时仅可看做ITL)的值传递到子线程;
- 子线程修改TTL的引用时,会造成内存不安全;
代码如下:
@Slf4j
@RestController
public class ThreadLocalController {
ExecutorService executorService =
TtlExecutors.getTtlExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()));
public static TransmittableThreadLocal<Map<String, String>> l2 = new TransmittableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<>();
}
};
/**
* 项目启动的时候,会调用TTL的get方法,这里使用static模拟;
*/
static {
l2.get();
log.info("项目启动时加载配置");
}
@RequestMapping("/local/t1")
public void t1() throws InterruptedException {
Map<String, String> mc = l2.get();
mc.put("t1", "t1v");
log.info("【/local/t1】主线程打印:" + l2.get());
executorService.execute(() -> {
log.info("【/local/t1】子线程2map{}", l2.get());
});
Thread.sleep(1000);
l2.remove();
}
@RequestMapping("/local/t4")
public void t4() {
log.info("【/local/t4】主线程打印:" + l2.get());
executorService.execute(() -> {
log.info("【/local/t4】子线程打印数据{}", l2.get());
});
Map<String, String> cache = l2.get();
cache.put("l4", "l4v");
l2.remove();
}
}
疑问:此时由于是普通的线程池,即使TTL重写copy方法也会造成线程不安全;
解决方法只有去重写childValue方法,来解决ITL传递到子线程吗?:
public static TransmittableThreadLocal<Map<String, String>> l2 = new TransmittableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<>();
}
@Override
protected Map<String, String> childValue(Map<String, String> parentValue) {
return initialValue();
}
};
3.7 创建出安全的TTL的方式
/**
* 环境变量数据
*/
private static final TransmittableThreadLocal<Map<String, String>> sealThreadLocalEnv = new TransmittableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new LinkedHashMap<>();
}
//解决普通线程池中使用TTL,造成数据污染的问题
@Override
protected Map<String, String> childValue(Map<String, String> parentValue) {
return initialValue();
}
//父子线程使用的是拷贝对象。而非简单对象的引用。
@Override
public Map<String, String> copy(Map<String, String> parentValue) {
return new LinkedHashMap<>(parentValue);
}
};