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

如何实现完成线程传递后的初始化操作 #383

Closed
fengzidk opened this issue May 27, 2022 · 1 comment
Closed

如何实现完成线程传递后的初始化操作 #383

fengzidk opened this issue May 27, 2022 · 1 comment
Assignees

Comments

@fengzidk
Copy link

fengzidk commented May 27, 2022

根据业务场景,在线程传递完成后需要进行一些默认的初始化操作
例如: 根据传递的ThreadLocal值 初始化子线程的数据源信息

目前TransmittableThreadLocal.set()方法不允许被重写,如何在不修改源码的前提下实现?

@oldratlee oldratlee added the ❓question Further information is requested label May 27, 2022
@oldratlee
Copy link
Member

oldratlee commented May 27, 2022

TransmittableThreadLocalThreadLocal/InheritableThreadLocal
(aka. TransmittableThreadLocalThreadLocal/InheritableThreadLocal的子类。)

线程传递时如何初始化

目前TransmittableThreadLocal.set()方法不允许被重写,如何在不修改源码的前提下实现?

@fengzidk 线程传递的初始化:

  • 可以并应该通过重写(override) 功能直接对应的 扩展方法:
    TransmittableThreadLocal#copy(T) 与/或 InheritableThreadLocal#childValue(T)
    • 具体要重写哪个还是2个都重写,由你的业务需求决定。
    • 这2个初始化方法不需要用户(主动在使用时)来调用。
  • 不要通过调用 功能不对应的ThreadLocal#set(T)方法
    • 需要用户主动调用,用户使用会相对麻烦。
    • 通过用户调用ThreadLocal#set(T)来做初始化操作,因为语义不对应(即系统设计错误),这样的实现方式往往会引发隐晦偶现的业务逻辑Bug

关于初始化方法

相关的初始化方法还有 ThreadLocal#initialValue()

这3个初始化方法 的区别是 做初始化操作的 生命周期时间点/触发时间 不同:

  1. ThreadLocal#initialValue()
    • ThreadLocal没有值时,取值(ThreadLocal#get()方法)触发初始化。
    • 下面是一些注意点
    • ThreadLocal#initialValue()Lazy的;
      即创建ThreadLocal实例时,并不会触发ThreadLocal#initialValue()调用。
    • 先设置值(ThreadLocal#set(T))再取值,则不会触发调用ThreadLocal#initialValue();因为已经有值了。
      即使设置的是null,也不会触发。
    • 调用ThreadLocal#remove()再取值,会触发调用ThreadLocal#initialValue();因为没有值了。
  2. InheritableThreadLocal#childValue(T)
    • 创建新线程时,用于初始化子线程的InheritableThreadLocal值。
  3. TransmittableThreadLocal#copy(T)
    • 传递时,用于初始化 在任务(如TtlRunnable)执行中的TransmittableThreadLocal值。

不同初始化方法是独立正交的

这里可能值得强调一下这3个初始化方法的容易被忽视的地方:3个方法是独立正交的。
(好的系统设计应该这样,不同功能独立正交;系统大的功能 是通过 方便地组合/使用更小的功能 来实现。)

展开一些的说明如下:

  • 因为生命周期时间点/触发时间 不同,各自独立发挥作用。
  • 子类包含进来的父类初始化方法,功能不变。
    • InheritableThreadLocalThreadLocal的子类,包含的ThreadLocal#initialValue()功能不变。
    • TransmittableThreadLocalInheritableThreadLocal的子类,包含的ThreadLocal#initialValue()InheritableThreadLocal#childValue(T)功能不变。

更多相关讨论参见

子线程remove/set操作不应该影响父线程。更进一步表述:

  • ThreadLocal值在不同线程之间是独立的(这其实是ThreadLocal的定义和命名)。
    • 即 更新操作 不会影响 别的线程的ThreadLocal值,这是正确的功能。
  • 在约定的时机会 传递
    • 这样的传递时机,作为功能 命名成如 InheritableTransmittable

系统设计最佳实践:结合业务扩展重写初始化方法,而不是用无业务含义的缺省实现

ThreadLocal的使用中,看到初始化方法的扩展重写没有得到应有的重视与理解使用。

即初始化方法没有重写,结果是在用无业务含义的缺省实现:

  • ThreadLocal#initialValue():返回null
  • InheritableThreadLocal#childValue(T):直接返回入参
  • TransmittableThreadLocal#copy(T):直接返回入参

这样的做法,导致 上面提到的『通过ThreadLocal#set(T)方法来完成初始化工作』。结果会导致:

  • 需要用户主动调用ThreadLocal#set(T)来,用户使用麻烦/困难。
  • 通过用户调用ThreadLocal#set(T)来做初始化操作,因为语义不对应(即系统设计错误),
    这样的实现方式往往会引发隐晦偶现的业务逻辑Bug

写一个示意Demo可以这样;但作为实际系统实现,

强烈推荐:结合业务与不同初始化方法的生命周期时间点 扩展重写好 相应的初始化方法。

用于重写初始化方法的TransmittableThreadLocal便利工具方法

TransmittableThreadLocal提供了下面3个静态工具方法withInitial*,可以方便地重写这些初始化方法的实现逻辑:
(如果没有静态工具方法withInitial*,就要写一个TransmittableThreadLocal子类来重写初始化方法,相对不方便)


@fengzidk 具体展开的更多说明,你可以先了解一下ThreadLocal及其子类。比如看看

如果你用上面的初始化方法不能实现你的需求,
可以贴一下示例代码 并展开说明需求与问题,我们一起看一下 ❤️ @fengzidk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants