Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于clean extra TTL value的疑问 #134

Closed
luoxn28 opened this issue Apr 20, 2019 · 3 comments
Closed

关于clean extra TTL value的疑问 #134

luoxn28 opened this issue Apr 20, 2019 · 3 comments
Assignees
Labels
❓question Further information is requested

Comments

@luoxn28
Copy link

luoxn28 commented Apr 20, 2019

您好,看到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:#90 #127

@oldratlee
Copy link
Member

oldratlee commented Apr 23, 2019

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的特性

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

  • ThreadLocal对象本身 一般是static的。
    ThreadLocal对象本身一般是全局存在的。
  • ThreadLocalvalueLazy Init(延迟初始化的)。
    • 初始化 是指 ThreadLocal在一个线程下有value了。否则value还不存在。
    • ThreadLocal为什么是延迟初始化,这个问题应该是容易理解的,原因是:
      在没有具体业务场景前提下,这样的做法避免内存浪费。
  • ThreadLocalget/set操作 会 初始化 ThreadLocalvalue

1.2 关于TransmittableThreadLocal的特性

  • TransmittableThreadLocal传递的是 TransmittableThreadLocalvalue
    并不是 TransmittableThreadLocal对象本身;TransmittableThreadLocal对象是全局的,本身是不需要传递。
  • 结合ThreadLocal value的延迟初始化与初始化特性,可以得出:
    需要传递场景,就需要显式保证value已经有了,即初始化了。
    (通过调用ThreadLocalget/set操作完成初始化)。

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

  • 系统功能及其流程
    1. 数据准备:由业务完成,往往对应 TransmittableThreadLocal#set()操作。
    2. 数据传递:由TTL库完成。
    3. 数据使用:由业务完成,对应 TransmittableThreadLocal#get()操作。
  • 从上面的流程可知,要传递哪些数据,是由业务决定的。
  • 『如果父线程中没set过,就出现在子线程(更严谨地说,是被传递的线程)』
    这其实是 Bug :) @luoxn28

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

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

按上面分析,总结一下:

  • 没有captured的值 不能/不应该 传递,否则 是 Bug。
    肯定 不期望数据 以不确定的方式 出现在 子线程中。
  • 要传递哪些数据,是由业务(逻辑)来决定 / 全权控制(通过在 合适位置 调用ThreadLocal#set方法来控制)。

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


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

@oldratlee oldratlee self-assigned this Apr 23, 2019
@oldratlee oldratlee added the ❓question Further information is requested label Apr 23, 2019
@luoxn28
Copy link
Author

luoxn28 commented Apr 24, 2019

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

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

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

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

@oldratlee
Copy link
Member

oldratlee commented Apr 24, 2019

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

你说得对。❤️

这样的功能或设计,

  • 一方面,避免 功能有缺陷:
    不能『要传递哪些数据,是由业务(逻辑)来决定 / 全权控制(通过在 合适位置 调用ThreadLocal#set方法来控制)』
  • 另一方面,避免『本来无值(没有set过的TransmittableThreadLocal变量),就出现在被传递线程』这样不期望不确定的效果(Bug,或说系统行为不确定)。

这个Issue 先Close了。 ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
❓question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants