Closed huhaosumail closed 1 year ago
关于「不修饰」runnable使用ttl导致子线「泄露」
首先请以正确的方式使用TTL
;参见项目文档 User Guide。
如果不做相应的修饰,TTL
会退化成InheritableThreadLocal
,
出现你说的情况是预期的(多提交几次可能非必现,与提交时间点/线程池配置相关);
更多请了解调研InheritableThreadLocal
与 ThreadPoolExecutor
。 @huhaosumail
其次请提供一个 极简的 可运行复现问题 的代码实现/Demo。推荐提供成一个单独的工程(Github repo),或是 一个运行出的问题的UT(可以Fork TTL加一个UT)。这样可以:
要不只有一个子线程能拿到上下文了。 (这样的功能,在日常业务中,是奇怪的、应该也无用)
如果这导致业务的内存泄露,可以自己在finally
中remove
一下。
更多TTL
的了解资料可以看看:
(JDK ThreadLocal
也有「内存泄露」的使用注意事项 💕)
请使用getDisableInheritableThreadFactory(...)
wrapper。
解决方法是
- 线程池线程不应该有无用的上下文,
- 或说 保证线程池线程刚开始时(所有业务逻辑的外层) 应该是 空的上下文,做好清空操作。
TTL
有讨论 & 提供了相应的功能实现: @OrientationZero
- 已有的讨论: disable Inheritable when it's not necessary and buggy(eg. has potential memory leaking problem) #100
- 对应
TTL
已实现的功能,即上面举例的DisableInheritableThreadFactoryWrapper
: https://github.com/alibaba/transmittable-thread-local/blob/86342990806ef94280941e31e529597ab775f611/src/main/java/com/alibaba/ttl/threadpool/TtlExecutors.java#L173-L197
更多说明参见已有 issue
感谢大佬,上面的Case自己也整理清楚了,说明如下:
这里的最主要是识别TransmittableThreadLocal
继承自JDK
的InheritableThreadLocal
,参考上面的代码当t1.set("val1")
,执行后相当于主线程InheritableThreadLocal
这个线程绑定的值有值了。
之后第一次ThreadPoolExecutor
执行异步任务的时候,会初始化线程,初始化线程的特性会子线程也继承父线程;也就是主线程的InheritableThreadLocal
。
这个时候相当于父子线程都携带了InheritableThreadLocal
,后续主线程remove
操作,remove
的是主线程的InheritableThreadLocal
,finally
执行第二次异步任务的时候,线程池的线程是携带InheritableThreadLocal
,所以导致能够取到值。
即便使用TtlRunnable.get(runnable)
修饰第二个任务,能够符合预期获取空值,因为修饰后获取到的是主线程已经remove
的InheritableThreadLocal
;
但是会发生泄漏的问题,并没有remove
掉子线程的InheritableThreadLocal
,需要使用
TtlExecutors.getDefaultDisableInheritableThreadFactory()
TransmittableThreadLocal<String> t1 = new TransmittableThreadLocal<String>() {
protected String childValue(String parentValue) {
return initialValue();
}
}
声明解决泄漏问题。
第一种是业务操作前 清空线程池线程上下文; 第二种是父子线程继承的时候 子线程初始化为空值。
@huhaosumail COOOL 👍 🚀
我想问下,下面这段逻辑,为什么
finally
的异步逻辑还是能获取到TTL
的值: