alibaba / transmittable-thread-local

📌 a missing Java std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.
https://github.com/alibaba/transmittable-thread-local
Apache License 2.0
7.59k stars 1.69k forks source link

主线程执行完后会remove,但是子线程执行完成后会restore恢复本地变量,子线程中的值变量如何清除? #533

Closed mingyang66 closed 1 year ago

mingyang66 commented 1 year ago

如下:主线程执行完后会remove,但是子线程执行完成后会restore恢复本地变量,子线程的本地变量如果不主动删除会不会OOM?

public class Test {
    private static final ThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newSingleThreadScheduledExecutor();
        System.out.println(1 + "-" + Thread.currentThread().getName() + ":" + CONTEXT.get());
        CONTEXT.set("parent");
        System.out.println(2 + "-" + Thread.currentThread().getName() + ":" + CONTEXT.get());
        service.submit(TtlRunnable.get(new Runnable() {
            @Override
            public void run() {
                System.out.println(3 + "-" + Thread.currentThread().getName() + ":" + CONTEXT.get());
                CONTEXT.set("sub");
                System.out.println(4 + "-" + Thread.currentThread().getName() + ":" + CONTEXT.get());
            }
        }));
        System.out.println(5 + "-" + Thread.currentThread().getName() + ":" + CONTEXT.get());
        CONTEXT.remove();
        System.out.println(6 + "-" + Thread.currentThread().getName() + ":" + CONTEXT.get());
    }
}
mingyang66 commented 1 year ago

TransmittableThreadLocal内部本身定义了holder变量如下:

    private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
            new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
                @Override
                protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
                    return new WeakHashMap<>();
                }

                @Override
                protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
                    return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
                }
            };

其中InheritableThreadLocal内部存储的是一个WeakHashMap类型,其键是一个WeakReference类型,如果没有强引用的话就会在下次GC的时候回收掉,而其value本身就是null,所以对于TTL本身来说是不存在内存溢出问题的;

对于子线程而言其值是存在于Thread线程的ThreadLocal.ThreadLocalMap inheritableThreadLocals变量中,而ThreadLocalMap内部的存储结构是一个WeakReference类型,如果没有强引用则会在下次GC的时候置为null,在下次使用的时候会主动清除掉key为null引用,从而避免OOM;

上述异步线程池中最外层主线程主动remove,内部无法主动remove,而不会造成OOM的原因是不是上述分析的 @oldratlee 帮忙解答下,谢谢

oldratlee commented 1 year ago

因为有「恢复(Transmitter.restore())」操作做了清除操作, 在运行完任务之后子线程(的ThreadLocal)不再持有上下文, 所以不会因为这个传递过程而引入内存泄露。 @mingyang66 注意:上面的过程 不包含「由Inheritable能力(InheritableThreadLocal) 带给子线程的上下文」这个情况。

对于Inheritable能力引起的「内存泄露」,有较多讨论, 可以看看网上的讨论、或这个库涉及「内存泄露」相关的issue。

TTL提供了关闭Inheritable的一些方法,具体参见TransmitableThreadLocal的JavaDoc。

mingyang66 commented 1 year ago

我看很多案例和issue中都没有调用TransmittableThreadLocal.remove方法移除上下文的操作,是不是因为TTL中使用WeakHashMap和ThreadLocal使用WeakRefrence的原因?GC的时候会自动回收?

oldratlee commented 1 year ago

我看很多案例和issue中都没有调用TransmittableThreadLocal.remove方法移除上下文的操作,是不是因为TTL中使用WeakHashMap和ThreadLocal使用WeakRefrence的原因?GC的时候会自动回收?

@mingyang66 上一条评论有原因的一些解释:

因为有「恢复(Transmitter.restore())」操作做了清除操作, 在运行完任务之后子线程(的ThreadLocal)不再持有上下文, 所以不会因为这个传递过程而引入内存泄露。 @mingyang66 注意:上面的过程 不包含「由Inheritable能力(InheritableThreadLocal) 带给子线程的上下文」这个情况。

如果想更多深入理解说明TTL的实现设计,

mingyang66 commented 1 year ago

非常感谢 @oldratlee 的回答,今天花了一天时间研究这一块,其实值传递的核心是

如果通过如下关闭Interitable能力:

          @Override
          protected User childValue(User parentValue) {
              return initialValue();
          }

那么在子线程replay回放时backup就不存在,所有在restore恢复的时候子线程其实是不存在值恢复的;

如果不关闭Interitable能力,那么子线程继承了父线程的值【普通对象、引用类型】,在restore后会将子线程的值恢复成从父线程继承的值或引用类型,但是如果子线程执行结束了,那么子线程的ThreadLocal就不会持有父线程的上下文了,这是因为ThreadLocal的生命周期是和线程的生命周期相关联,线程结束,则ThreadLocal变量也会自动被GC销毁;

还有个问题,就是线程池复用的问题,如果不关闭线程的Interitable能力,则子线程继承父线程的引用对象,如果执行完成后被线程池复用,那么就会有一部分引用对象一直无法释放,如果这个量比较大的话有可能造成OOM,像这种问题又是如何解决的呢?

有空了帮忙解答下,谢谢!

mingyang66 commented 1 year ago

我在https://github.com/alibaba/transmittable-thread-local/issues/521这个ISSUE中找到了想要的答案, @oldratlee 感谢