Java 从2.1.4升级到3.4.3后,ThreadLocal get()有时返回null
代码如下:Java 从2.1.4升级到3.4.3后,ThreadLocal get()有时返回null,java,multithreading,concurrency,thread-safety,thread-local,Java,Multithreading,Concurrency,Thread Safety,Thread Local,代码如下: public class ContextManagerImpl implements ContextManager { private static final ThreadLocal<Context> ctx = new ThreadLocal<Context>(); @Override public Context getContext() { if(ctx.get() == null) {
public class ContextManagerImpl implements ContextManager {
private static final ThreadLocal<Context> ctx = new ThreadLocal<Context>();
@Override
public Context getContext() {
if(ctx.get() == null) {
ctx.set(new Context("", "")); // Dummy context. This should never happen
}
return ctx.get();
}
@Override
public void begin(Context context){
ctx.set(context); // Verified context passed is never null or blank
}
@Override
public void end() {
if(ctx!= null) {
ctx.remove();
}
}
}
public final class Context implements Serializable {
private String s1;
private String s2;
}
公共类ContextManagerImpl实现ContextManager{
private static final ThreadLocal ctx=new ThreadLocal();
@凌驾
公共上下文getContext(){
if(ctx.get()==null){
ctx.set(新上下文(“,”);//虚拟上下文。这种情况永远不会发生
}
返回ctx.get();
}
@凌驾
公共void开始(上下文){
ctx.set(context);//传递的已验证上下文从不为null或空
}
@凌驾
公共无效结束(){
如果(ctx!=null){
ctx.remove();
}
}
}
公共最终类上下文实现了可序列化{
私有字符串s1;
私有字符串s2;
}
有2个线程正在使用具有不同threadlocal上下文的此类。它在大多数情况下都可以正常工作,但是有时即使begin()在ctx中正确设置了值,getContext()也会返回虚拟上下文。
我怀疑某个地方存在导致这种情况的竞争条件,但鉴于threadlocal set()和get()是线程安全的,并且ctx的初始化是在声明过程中完成的,所以这种情况永远不会发生。
注意:我已经将spring boot升级到3.4.3,但仍然使用JDK 8
从2.1.4升级到3.4.3后,ThreadLocal get()有时返回null
如果Spring保证在getContext()
之前调用begin(…)
,那么在我看来,一个线程正在调用begin(…)
方法,而另一个线程正在调用getContext()
。如果是这种情况,getContext()
将返回null
ThreadLocal
旨在为每个线程提供不同的上下文。这就是你想要的吗?我不完全理解您的接线,但我怀疑您使用了ThreadLocal
。关于线程的spring boot保证是什么
我怀疑您应该将其设置为一个易失性字段:
private static volatile Context ctx; // maybe set to new Context("", "");
// this should be called by spring boot
@Override
public Context begin(Context ctx) {
this.ctx = ctx;
}
public Context getContext() {
return ctx;
}
如果您担心一些竞争条件,如果您的ContextManagerImpl
的多个实例正在使用相同的上下文初始化,那么您可以使用AtomicReference
:
// may want to initialize with a dummy context
private static AtomicReference <Context> ctxRef = new AtomicReference();
// this should be called by spring boot
@Override
public Context begin(Context ctx) {
ctxRef.compareAndSet(null, ctx);
}
public Context getContext() {
return ctxRef.get();
}
//可能需要使用虚拟上下文初始化
私有静态AtomicReference ctxRef=新的AtomicReference();
//这应该由spring boot调用
@凌驾
公共上下文开始(上下文ctx){
ctxRef.compareAndSet(null,ctx);
}
公共上下文getContext(){
返回ctxRef.get();
}
Yes保证在getContext()之前调用begin()。是的,我希望每个线程获得不同的上下文。如果我使用variable volatile或AtomicReference,那么两个线程将不会有不同的副本。问题是,对于线程,即使调用begin()来设置上下文,getContext()有时也会返回null。假设线程被设计为为为每个线程提供不同的上下文,那么两个线程不应该相互干扰。不知道我错过了什么。想知道这是否是因为spring boot 3.4.3和jdk 8的兼容性?如果thread1调用开始设置上下文,然后Thread2调用getContext(),它会在@pankajsachan处得到null?我真的不认为这是你想要的。抱歉@Gray,如果我之前不清楚的话,但是两个线程都会在调用getContext()之前调用begin(),因此不应该为null。你确定@pankajsachan每个调用getContext()
的线程都会首先调用begin(…)
?我怀疑情况并非如此,因为您得到的是空值。如果你得到一个空值,那么你的代码就不会像你想象的那样被调用。简单明了。为了提供更多详细信息,当Grpc请求到来时,我在拦截器中调用begin(…)
,然后在Grpc服务器实现类中处理请求的过程中调用getContext(…)
,获取我在begin(…)
中设置的头等详细信息。我做了更多的阅读,发现不能保证grpc请求和响应将在同一个线程中完成,因此,当grpc请求作为线程X的一部分出现时,可能会在该线程中调用begin(…)
,并在线程Y中使用getContext(…)
进行处理/响应?不过这只是一个理论。