如何与子线程共享父 ThreadLocal 对象引用?
How to share parent ThreadLocal object reference with the Child threads?
用例
我有一个基于 gRPC+Guice 的服务应用程序,其中对于特定调用,代码流如下所示:A -> B -> C
和 A -> X -> Y
对于特定服务请求。
其中,A = 顶级服务 operation/Activity class; B = Class 以 class C 作为任务创建 ExecutorService 线程池; X 和 Y 正常 classes.
我想要一个跨 class B、C 和 Y 这些 class 的共享对象 ContainerDoc
,但不想传递给方法参数。所以,我决定使用 InheritableThreadLocal。
但我想了解如何强制将父 ThreadLocal ContainerDoc
共享给子线程,以便在 ContainerDoc
中完成的任何 更新 子线程对父线程也是可见的?
- 覆盖 childValue 方法 到 return 与父对象相同的包含对象是否使其工作? (见下面的实现)。
- 如何保证线程安全?
示例实现
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 之前。它可能比将它作为参数传递更难看。
用例
我有一个基于 gRPC+Guice 的服务应用程序,其中对于特定调用,代码流如下所示:A -> B -> C
和 A -> X -> Y
对于特定服务请求。
其中,A = 顶级服务 operation/Activity class; B = Class 以 class C 作为任务创建 ExecutorService 线程池; X 和 Y 正常 classes.
我想要一个跨 class B、C 和 Y 这些 class 的共享对象 ContainerDoc
,但不想传递给方法参数。所以,我决定使用 InheritableThreadLocal。
但我想了解如何强制将父 ThreadLocal ContainerDoc
共享给子线程,以便在 ContainerDoc
中完成的任何 更新 子线程对父线程也是可见的?
- 覆盖 childValue 方法 到 return 与父对象相同的包含对象是否使其工作? (见下面的实现)。
- 如何保证线程安全?
示例实现
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 之前。它可能比将它作为参数传递更难看。