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

关于clean extra TTL value的疑问 #134

Closed luoxn28 closed 5 years ago

luoxn28 commented 5 years ago

您好,看到clean extra TTL value这块逻辑,有个疑问,特请教下,谢谢。

疑问点:

从目前transmittable-thread-local的TtlRunnable实现来看,在TtlRunable run时,会以TtlRunnable.get时间点获取的captured(类似TTL快照)为准,holder中不在captured的先移除,在的会被替换

holder中不在captured的先移除,这样会导致当前线程之前set过的TransmittableThreadLocal变量数据丢失,如果父线程中也存在该TransmittableThreadLocal变量但是没set过?

public static Object replay(@Nonnull Object captured) {
    @SuppressWarnings("unchecked")
    Map<TransmittableThreadLocal<?>, Object> capturedMap = (Map<TransmittableThreadLocal<?>, Object>) captured;
    Map<TransmittableThreadLocal<?>, Object> backup = new HashMap<TransmittableThreadLocal<?>, Object>();

    for (Iterator<? extends Map.Entry<TransmittableThreadLocal<?>, ?>> iterator = holder.get().entrySet().iterator();
         iterator.hasNext(); ) {
        Map.Entry<TransmittableThreadLocal<?>, ?> next = iterator.next();
        TransmittableThreadLocal<?> threadLocal = next.getKey();

        // backup
        backup.put(threadLocal, threadLocal.get());

        // clear the TTL values that is not in captured
        // avoid the extra TTL values after replay when run task
        // 这里会排除当前线程holder中非captured来的所有ThreadLcoal,在restore时在重新放回来
        if (!capturedMap.containsKey(threadLocal)) {
            iterator.remove();
            threadLocal.superRemove();
        }
    }

    // set TTL values to captured
    setTtlValuesTo(capturedMap);

    // call beforeExecute callback
    doExecuteCallback(true);

    return backup;
}

这里我有个疑问,如果当前线程holder中有一个TransmittableThreadLocal(captured中没有),并且当前线程之前也set过该TransmittableThreadLocal,执行到上述逻辑时,当前线程的TransmittableThreadLocal变量数据丢失,示例代码如下:

ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(() -> {});

TransmittableThreadLocal<String> child = new TransmittableThreadLocal<>();
executor.submit(() -> {
    child.set("value-set-in-child");
    System.out.println(Thread.currentThread().getName() + ": " + child.get());
});

final TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<>();
parent.set("value-set-in-parent");
executor.submit(TtlRunnable.get(() -> {
    System.out.println(Thread.currentThread().getName() + ": " + child.get()); // 这里child.get为null
    System.out.println(Thread.currentThread().getName() + ": " + parent.get());
}));

executor.shutdown();

输出结果为:

pool-1-thread-1: value-set-in-child
pool-1-thread-1: null
pool-1-thread-1: value-set-in-parent

从目前transmittable-thread-local的TtlRunnable实现来看,在TtlRunable run时,会以TtlRunnable.get时间点获取的captured(类似TTL快照)为准,holder中不在captured的先移除,在的会被替换。这样处理的话,相当于针对TransmittableThreadLocal来说,都是以captured为最高优先级了。

这样来看,TransmittableThreadLocal在TtlRunnable中不像一个ThreadLocal,是一个父线程Transmittable上下文的映射(个人理解),而在普通Runable中才像一个ThreadLocal。

类似issue:https://github.com/alibaba/transmittable-thread-local/issues/90 https://github.com/alibaba/transmittable-thread-local/issues/127

oldratlee commented 5 years ago

holder中不在captured的先移除,这样会导致当前线程之前set过的TransmittableThreadLocal变量数据丢失,如果父线程中也存在该TransmittableThreadLocal变量但是没set过?

@luoxn28 关于为什么 这样的功能或设计 的 原因,简单说明解释是:

TransmittableThreadLocal变量虽然存在,但还没有value,所以 不能/不会传递。

否则系统中所有的一大把的TransmittableThreadLocal都被传递了。 额外的性能开销问题先不说,更重点的是:传递哪些上下文的,你的实现逻辑 控制不了

ThreadLocalvalueLazy Init(延迟初始化的)。 需要通过 get/set操作 完成 ThreadLocalvalue初始化。 (关于ThreadLocal延迟初始化,可以了解ThreadLocal相关的资料)


更展开的说明如下 (TL;DR :)

分2部分说明:

  1. 先从 ThreadLocalTransmittableThreadLocal 2个类的特性 角度说明
  2. 再从 功能/系统设计 角度说明

1.1 关于ThreadLocal的特性

# 也是InheritableThreadLocalTransmittableThreadLocal的特性,因为这2个本身都是ThreadLocalis-a,子类)。

1.2 关于TransmittableThreadLocal的特性

2. 功能/系统设计 角度的说明

最后再回到你的说的 『问题』上

如果当前线程holder中有一个TransmittableThreadLocalcaptured中没有),并且当前线程之前也set过该TransmittableThreadLocal,执行到上述逻辑时,当前线程的TransmittableThreadLocal变量数据丢失

按上面分析,总结一下:

并没有 你说的『数据丢失』的问题 :)


看看我解释清楚了吗?欢迎讨论。 @luoxn28 ❤️

luoxn28 commented 5 years ago

谢谢回复~ 你说的这些我是理解的,TTL代码也是这样实现的。

如果父线程中没set过,就出现在子线程(更严谨地说,是被传递的线程)』 这其实是 Bug :)

使用transmittable-thread-local,是不推荐这种父线程中没有set过的TransmittableThreadLocal变量,就出现在被传递线程的。

我当时是看到clean extra TTL value这块代码时感觉这种场景有问题,实际上不推荐这样使用的~

oldratlee commented 5 years ago

@luoxn28 实际上不推荐这样使用的

你说得对。❤️

这样的功能或设计,

这个Issue 先Close了。 ❤️