Closed yexuerui closed 3 years ago
因为
TTL
底层使用ITL
,会导致在new
线程的时候,父子线程的数据传递,且无法销毁。背景:
- 项目启动的时候,存在
TTL
的get
操作,于是main
线程存在TTL
的value
;- 当请求进入时,
Tomcat
线程池(不会被TtlExecutors
装饰)会开启子线程来执行业务逻辑;main
线程会将TTL
(此时仅可看做ITL
)的值传递到子线程;- 子线程修改
TTL
的引用时,会造成内存不安全;
Inheritable
能力/功能 引发的问题其中,ITL
(InheritableThreadLocal
)引发的问题 在 前一个你的 Issue https://github.com/alibaba/transmittable-thread-local/issues/281#issuecomment-869000689 中,说明了问题与解法。
对于你的场景是线程池;线程池是业务逻辑无关的,应该disable Inheritable。
更方便、合理的解决方法可以是:
通过设置线程池的ThreadFactory
成DisableInheritableThreadFactory
,disable线程池的Inheritable。
对应你的示例代码,修改如下: @yexuerui
public class ThreadLocalController {
ExecutorService executorService = TtlExecutors.getTtlExecutorService(
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
// *disable Inheritable*
// 通过设置线程池的ThreadFactory成DisableInheritableThreadFactory
TtlExecutors.getDefaultDisableInheritableThreadFactory()
)
);
......
}
疑问:此时由于是普通的线程池,即使TTL重写copy方法也会造成线程不安全;
TTL
提供了 数据的传递的能力(作为ThreadLocal
也持有了数据);
而传递的对象的线程安全问题,需要业务逻辑来解决。 @yexuerui
TTL
自身的内部数据有并发问题,则是TTL
的Bug;TTL
的外部数据,可以足够复杂。
TTL
控制接管不了(当然TTL
也没有去接管 :")。通用基础的并发问题,既不限于也独立于 TTL
的使用:
只要一个对象传递到了不同的线程(不再有线程封闭),就需要关注这个对象的线程安全问题。
JDK
的InheritableThreadLocal
类,业务使用方也一样有线程安全问题 需要注意:InheritableThreadLocal.childValue
方法 也 可以实现成 将一个对象引用传递到另一个线程,需要注意&解决线程安全问题。文档 User Guide:
线程安全/并发安全 的通用解决思路:
Immutable
)
String
。String
是Immutable
的,所以线程安全。HashMap
,但浅拷贝了,保证了Map
这一级的线程安全。
Map
里的KV
仍然需要继续设计/实现 以保证线程安全。(重复应用这份通用解决思路)MyXxxContext
类。
MyXxxContext
实现成是可以并发的。Map
引用,则可以用ConcurrentHashMap
,以保证Map
这一级的线程安全。
Map
里的KV
仍然需要继续设计/实现 以保证线程安全。(重复应用这份通用解决思路)注意:线程安全 不代表 业务逻辑正确。业务逻辑正确 还和你的业务流程设计 相关。
这里不再展开 线程安全 的更多讨论了。
您好。我明白可以装饰ThreadFactory来解决ITL的问题。
但是我重点强调的是:
请求进来时,是tomcat开启一个线程处理; 但是tomcat的线程池没有使用ttl的包装的线程池,也就无法使用您说的上面的方法。
我重点强调的是:
请求进来时,是tomcat开启一个线程处理; 但是tomcat的线程池没有使用ttl的包装的线程池,也就无法使用您说的上面的方法。
childValue
的方式,也可以做到 线程安全
new Thread
时,这个TTL
实例就不会传递了
childValue
的方式,关闭了这个TTL
实例的Inheritable
能力。如果只希望在Tomcat
线程池中关闭Inheritable
,可以的做法是:
Tomcat
Tomcat
线程池的ThreadFactory
,Wrap成DisableInheritableThreadFactory
Tomcat
与TTL
的相关Issue好的,我理解您的意思了
好的,我理解您的意思了
👍 👏 🎉 @yexuerui
带上 并发/多线程 维度时,要想解释清楚,是比较费时费力的~ 🤣 🤯
之前的Issue,涉及并发多线程时,
我一般简单说明,这些并发使用问题与TTL
功能是独立正交的,
尽量避免展开去解释。 😅
public static final TransmittableThreadLocal<ConcurrentMap<String, Object>> THREAD_CONTEXT = TransmittableThreadLocal.withInitial(() -> {
return new ConcurrentHashMap<>();
});
@oldratlee 你好,
TTL
作为一个公共静态常量使用,应用全局使用该常量ThreadFactory
设置为了TtlExecutors.getDefaultDisableInheritableThreadFactory()
但是依然存在线程安全问题。
问题表现为,
Spring EventBus
的使用中、高并情况下,TTL
的值,事件消费者(B线程池,Java agent
装饰)消费新事件时,尝试过-javaagent
装饰线程池的方式,也与手动装饰的方式一同使用,但是依然存在该问题。
问题是否跟static
和final
的线程安全性有关?ConcurrentMap
应该是安全的。
由于业务相对复杂,但是我尝试用测试用例复现,却没能复现出来 😭 , 所以暂时没有示例,不知我表达清楚没有,还望答疑解惑一下,谢谢。
@HuangDayu 独立的问题,请开个新的 issue。 🙏
上面你列的这些前提,如
TTL
作为一个公共静态常量使用ThreadFactory
设置为了TtlExecutors.getDefaultDisableInheritableThreadFactory()
-javaagent
装饰线程池的方式 or 手动装饰的方式ConcurrentMap
是安全的并不能保证 在你业务中 取得 你期望的新值或旧值。
『并不能保证』的一个简单举例 就是
TTL
值。如果不能排除『一段在你业务之中你意料之外的逻辑 改写了 TTL
值』,
因为论证逻辑不完整,不能得到『会是什么值』的相关结论。
基础件(如TTL
、ConcurrentMap
)出问题的概率很小(因为被大量使用与验证)。
当然确保你正确地理解与使用了这些基础件。
一个复现Demo,因为有全部的运行逻辑代码,可以用于排除或证实
TTL
值』。PS: 能方便确定 没有『业务之中意料之外的逻辑』, 是 良好系统设计的目标与体现。比如 做好封装。
@oldratlee 你好,我已参照TtlMDCAdapter 解决了该问题,非常感谢你的解答,谢谢。
因为
TTL
底层使用ITL
,会导致在new
线程的时候,父子线程的数据传递,且无法销毁。背景:
TTL
的get
操作,于是main
线程存在TTL
的value
;Tomcat
线程池(不会被TtlExecutors
装饰)会开启子线程来执行业务逻辑;main
线程会将TTL
(此时仅可看做ITL
)的值传递到子线程;TTL
的引用时,会造成内存不安全;代码如下:
疑问:此时由于是普通的线程池,即使
TTL
重写copy
方法也会造成线程不安全;解决方法只有去重写
childValue
方法,来解决ITL
传递到子线程吗?: