如何与子线程共享父 ThreadLocal 对象引用?

How to share parent ThreadLocal object reference with the Child threads?

用例

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

其中,A = 顶级服务 operation/Activity class; B = Class 以 class C 作为任务创建 ExecutorService 线程池; X 和 Y 正常 classes.

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

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

  1. 覆盖 childValue 方法 到 return 与父对象相同的包含对象是否使其工作? (见下面的实现)。
  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();
    }
}

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

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

Returning same reference but I think this value gets copied over to each Child thread as separate object

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

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

ThreadLocal 是一种解决第三方代码中无法更改的设计缺陷的工具。即使可行,也是不得已而为之——而在这里,它行不通。

根据需要将 ServiceDoc 作为方法或构造函数参数传递给 B、C 和 Y。

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