【问题标题】:How to share parent ThreadLocal object reference with the Child threads?如何与子线程共享父 ThreadLocal 对象引用?
【发布时间】:2021-07-27 17:13:44
【问题描述】:

用例

我有一个基于 gRPC+Guice 的服务应用程序,其中对于特定调用,代码流如下所示:A -> B -> CA -> X -> Y 对于特定服务请求。

其中,A = 顶级服务操作/Activity 类; B = 以 C 类作为任务创建 ExecutorService 线程池的类; X 和 Y 是普通类。

我想要一个跨类 B、C 和 Y 这些类的共享对象 ContainerDoc,但不想传递给方法参数。所以,我决定使用 InheritableThreadLocal。

但我想了解如何强制将父 ThreadLocal ContainerDoc 共享给子线程,以便子线程在 ContainerDoc 中完成的任何 更新 对父线程也可见?

  1. 重写 childValue 方法 是否返回与父对象相同的包含对象以使其工作? (见下文实施)。
  2. 如何确保线程安全

示例实施

class ContainerDoc implements ServiceDoc {
    private final Map < KeyEnum, Object > containerMap;

    public ContainerDoc() {
        this.containerMap = new HashMap < KeyEnum, Object > ();
        // Should it be ConcurrentHashmap to account for concurrent updates?
    }

    public < T > T getEntity(final KeyEnum keyEnum) {
        return (T) containerMap.get(keyEnum);
    }

    public void putEntity(final KeyEnum keyEnum, final Object value) {
        entities.put(keyEnum, value);
    }

    enum KeyEnum {
        Key_A,
        Key_B;
    }

}

public enum MyThreadLocalInfo {

    THREADLOCAL_ABC(ContainerDoc.class, new InheritableThreadLocal < ServiceDoc > () {

            // Sets the initial value to an empty class instance.
            @Override
            protected ServiceContext initialValue() {
                return new ContainerDoc();
            }

            // Just for reference. The below impl shows default 
            // behavior. This method is invoked when any new
            // thread is created from a parent thread.
            // This ensures every child thread will have same 
            // reference as parent.
            @Override
            protected ServiceContext childValue(final ServiceDoc parentValue) {
                return parentValue;
                // Returning same reference but I think this 
                // value gets copied over to each Child thread as 
                // separate object instead of reusing the same 
                // copy for thread-safety. So, how to ensure 
                // using the same reference as of parent thread?
            }
        }),
        THREADLOCAL_XYZ(ABC.class, new InheritableThreadLocal < ServiceDoc > () {
            ....
            ....
        });

    private final Class << ? extends ServiceDoc > contextClazz;

    private final InheritableThreadLocal < ServiceDoc > threadLocal;

    MyThreadLocalInfo(final Class << ? extends ServiceDoc > contextClazz,
        final InheritableThreadLocal < ServiceDoc > threadLocal) {
        this.contextClazz = contextClazz;
        this.threadLocal = threadLocal;
    }

    public ServiceDoc getDoc() {
        return threadLocal.get();
    }

    public void setDoc(final ServiceDoc serviceDoc) {
        Validate.isTrue(contextClazz.isAssignableFrom(serviceDoc.getClass()));
        threadLocal.set(serviceDoc);
    }

    public void clearDoc() {
        threadLocal.remove();
    }
}

客户端代码(来自子线程类或常规类

MyThreadLocalInfo.THREADLOCAL_ABC.setDoc(new ContainerDoc());
MyThreadLocalInfo.THREADLOCAL_ABC.getDoc().put(Key_A, new Object());
MyThreadLocalInfo.THREADLOCAL_ABC.clearDoc();

【问题讨论】:

    标签: java multithreading concurrency thread-local inheritable-thread-local


    【解决方案1】:

    返回相同的引用 但我认为这个值会作为单独的对象复制到每个子线程中

    运行时如何实例化这样一个“单独的对象”?这个理论是不正确的。您的childValue() 实现与默认实现完全相同。

    InheritableThreadLocal 在创建新线程时根据父级分配一个值ExecutorService 可以有任何实现,您无需指定您的线程如何创建,但对于您的工作方法,父线程需要设置值,创建一个新线程,然后使用该新线程执行任务。换句话说,它只能与未池化的线程一起使用。

    ThreadLocal 是解决第三方代码中无法更改的设计缺陷的工具。即使它有效,它也是最后的手段——而在这里,它不起作用。

    ServiceDoc 作为必要的方法或构造函数参数传递给 B、C 和 Y。

    这可能意味着 X 也需要传递 ServiceDoc,但是,由于 X-Y 代码路径中不涉及 Executor,因此 A 可以在调用 X 之前有条件地初始化 ThreadLocal。这可能更丑陋而不是将其作为参数传递。

    【讨论】:

    • 1. “子线程作为单独的对象”=>它是在子线程创建期间根据 ThreadLocal 的实现为与 ExecutorService 关联的 ThreadPool 完成的(根据我快速了解 createInheritedMap() 方法如何使用 childValue() 方法)所以子线程没有作为父母的确切参考。 2.“你的childValue()实现和默认的完全一样。” => 是的,我知道,这只是为了参考(更新了代码上的 cmets)。
    • 3. “只能使用非池线程” => 是的,因此我正在寻找替代方案。我们可以使用@RequestScoped 注入之类的东西吗?但似乎内部也使用 ThreadLocal,所以会有类似的担忧。 4.“第三方代码”=> 至少就我而言,我没有使用第三方代码。你这里还有别的意思吗? 5.“将 ServiceDoc 作为参数传递”=> 是的,这是我最后的手段。只会在 A 类中初始化 ThreadLocal 一次,并且仅在 B 类和 Y 类中从 ThreadLocal 中获取 ServiceDoc。 Y 可以直接使用,B 会将 ServiceDoc 传递给任务 C。所以,总共 2 次访问。
    • @bit_cracker007 子线程肯定确实具有与父线程相同的引用。也就是说,(如果您不覆盖childValue())调用get() 返回给子线程的初始值与返回给父线程的初始值相同。如果您谈论的是本地线程内部使用的映射,是的,它们是不同的,因为如果子线程重新分配 本地线程, 其他线程,包括父线程,一定不会看到这种变化。为简化起见,子线程有自己的“变量”,但最初设置为与父线程相同的值。
    • 是的,我指的是 ThreadLocal 的 createInheritedMap() 方法。抱歉,您的第一行和最后一行似乎有点矛盾(也许是语言)。我猜你的意思是将父上下文的浅拷贝传递给子上下文?
    • 再问一个问题,以防您愿意回答:stackoverflow.com/questions/68555018/… :)
    猜你喜欢
    • 2011-09-13
    • 1970-01-01
    • 2012-12-13
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多