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(…)
进行处理/响应?不过这只是一个理论。