Closed cubika closed 5 years ago
TransmittableThreadLocal 在线程切换时进行上下文备份和回放,为什么使用 HashMap 而非 WeakHashMap 进行存储?
Capture
+ Replay
+ Restore
(即CRR
) 是为了完成 业务 上下文的传递,所以一定要传递成功。
如果CRR
过程用的是WeakHashMap
,则传递过程中 可能会被释放掉;即可能 传递失败,即是 Bug。 @cubika
可能会被释放掉 的原因是:
TransmittableThreadLocal
中持有的上下文是 弱引用。
ThreadLocal
和InheritableThreadLocal
也是这样。ScheduledThreadPoolExecutor#scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
提交延迟执行的任务。把原来是弱引用的 key 变成了强引用,可能导致父线程已经执行完了,但由于子线程使用了 TransmittableThreadLocal 变相延长了生命周期,restore 返回之前无法被回收。
因为 子线程 要使用上下文,所以保持 上下文的生命周期(即不被回收) 是 预期的,否则即上下文传递失败。
在子线程中可以读取,值是"value-set-in-parent" 此时前99个按理说已经没有强引用可以被回收了,回收后如果触发到清理逻辑,对应的value也会被删除 但是由于 capture 等操作的存在,前99个key变成强引用,子线程执行中时是无法被回收的
上下文传递后,任务对象的生命周期由任务本身的需求来决定,所有任务对象释放 且 父线程不再持有上下文(即上下文没有人需要使用了),上下文无强引用会被释放。
即『此时前99个按理说已经没有强引用可以被回收了,回收后如果触发到清理逻辑,对应的value也会被删除』这个不能成立,因为任务还需要使用上下文,对应的value不能被删除。 这点以上面的『延迟执行的任务』作为Case比较容易理解这个设计方式。
理解了,因为是异步的,子线程并不强保证执行顺序(即使代码看起来是立即执行的),可能存在延迟执行的情况,这时如果父线程上下文被回收,子线程就拿不到需要的数据,从而产生 bug
所有权 确实是个『费脑子』的问题。
经过仔细的分析:
CRR
:Capture
+ Replay
+ Restore
)中,确实是应该使用WeakHashMap
,而不是HashMap
。 @cubika 👍实现代码已经改好了,并发布了版本2.11.0
:
https://github.com/alibaba/transmittable-thread-local/releases/tag/v2.11.0
- use WeakHashMap instead of HashMap for capture/backup snapshot 🚢
- TTL do NOT have the ownership of thread local/snapshot!
- more gc friendly, avoid potential memory leak!
请问 TransmittableThreadLocal 在线程切换时进行上下文备份和回放,为什么使用 HashMap 而非 WeakHashMap 进行存储?
对应代码:
Map<TransmittableThreadLocal<?>, Object> captured = new HashMap<TransmittableThreadLocal<?>, Object>();
Map<TransmittableThreadLocal<?>, Object> backup = new HashMap<TransmittableThreadLocal<?>, Object>();
这样把原来是弱引用的 key 变成了强引用,可能导致父线程已经执行完了,但由于子线程使用了 TransmittableThreadLocal 变相延长了生命周期,restore 返回之前无法被回收。