alibaba / jetcache

JetCache is a Java cache framework.
Apache License 2.0
5.08k stars 1.05k forks source link

cause by java.lang.NullPointerException: no decoder for identity number:-153049664 #852

Open zhaoguozhenjava opened 8 months ago

zhaoguozhenjava commented 8 months ago

cause by java.lang.NullPointerException: no decoder for identity number:-153049664 项目刚启动时有并发读redis缓存会报错,因为DecoderMap类initDefaultDecoder方法没有执行完初始化的时候,调用register就把inited设置成了true,导致没有初始化完就有其他线程使用报错

areyouok commented 8 months ago

DecoderMap操作都加锁的,应该不存在并发问题。

如果你自己调用了register,那么就是想定制自己的序列化策略,所以此时initDefaultDecoder不再生效,你应该把自己需要的decoder都注册一下。

zhaoguozhenjava commented 8 months ago

没有主动调用,用的Kryo5ValueDecoder, -153049664就是Kryo5序列化反序列化的标记。 机器刚启动的时候反序列化的时候报no decoder for identity number:-153049664;如果正常初始化的话拿到的应该就是Kryo5ValueDecoder发序列化,不应该有问题; 2.7.2和2.7.3的版本都没有加锁的; 最新的代码里是有锁,但是锁是在读volatile变量inited之后的,所以正常并发的情况下有可能读到了未完全初始化的数据,导致报异常

areyouok commented 8 months ago

以前没锁但是有synchronized,一样的。没看出这里有并发问题。

zhaoguozhenjava commented 8 months ago

initDefaultDecoder方法没有锁啊,两个线程可以同时进入initDefaultDecoder方法; A线程进入了synchronized块里,且执行了一行register(SerialPolicy.IDENTITY_NUMBER_JAVA, defaultJavaValueDecoder()); 此时B线程执行if (inited) 将会返回true,但是A线程的initDefaultDecoder方法还没有执行完毕,其余的valueDecoder还没有注册进去,导致B线程拿不到valueDecoder

areyouok commented 8 months ago

synchronized代码块怎么会有两个线程同时进去?

就算这里没有加任何线程安全措施,发生并发问题的概率也是极低的。你的问题如果是每次都出现的,应该从别的地方找原因。 要么是自己调用了register没有注册KRYO5,要么是低版本的jetcache(没有注册kryo5反序列化器)读了一个高版本jetcache写进去的kryo5方式序列化的数据。

zhaoguozhenjava commented 8 months ago

synchronized代码块不会有两个线程进入,但是initDefaultDecoder方法可以,initDefaultDecoder方法没有加同步控制; 概率是比较低,就第一次启动的时候报错,jetcache版本一直没变过; 我这边用了线程池多个线程同时去获取了缓存,就存在了竞争,出来了并发的问题

zhaoguozhenjava commented 8 months ago

如果register我自己调用的话就不来这里提问啦,肯定没有调用,谢谢

areyouok commented 8 months ago

我不明白你的意思,我也没看出来现在的代码有什么问题(除了上面提到的两个问题:用户自己调用register没有注册完全,或者版本问题)。

areyouok commented 8 months ago

还有你要注意锁里面办完事了才设置inited为true,这是很常见的优化手法,保证正确性的同时优化性能,后续调用这个方法都不必加解锁

zhaoguozhenjava commented 8 months ago

public synchronized void register(int identityNumber, AbstractValueDecoder decoder) { decoderMap.put(identityNumber, decoder); inited = true; // initDefaultDecoder没执行完就改写了true啊 } public synchronized void clear() { decoderMap.clear(); inited = true; }

public void initDefaultDecoder() {
 //多个线程可以同时进入该方法
    if (inited) {
        return;
    }
    synchronized (this) {
        if (inited) {
            return;
        }
        register(SerialPolicy.IDENTITY_NUMBER_JAVA, defaultJavaValueDecoder());
        // A线程执行到了这一刻,inited已经改写成true了,B线程发现是true就返回了,但是后面的KryoValueDecoder,Kryo5ValueDecoder还没有注册进去啊
        register(SerialPolicy.IDENTITY_NUMBER_KRYO4, KryoValueDecoder.INSTANCE);
        register(SerialPolicy.IDENTITY_NUMBER_KRYO5, Kryo5ValueDecoder.INSTANCE);
        // register(SerialPolicy.IDENTITY_NUMBER_FASTJSON2, Fastjson2ValueDecoder.INSTANCE);
        inited = true; //这里好像没用啊,上面已经设置true了
    }
}

DecoderMap类initDefaultDecoder方法没有执行完初始化的时候,调用register就把inited设置成了true(### 这里说的是initDefaultDecoder方法调用了register不是说用户自己调用****),导致没有初始化完就有其他线程使用报错(A线程调用initDefaultDecoder方法执行到register(SerialPolicy.IDENTITY_NUMBER_JAVA, defaultJavaValueDecoder())的时候,把inited改成了true,此时register(SerialPolicy.IDENTITY_NUMBER_KRYO5, Kryo5ValueDecoder.INSTANCE还没有执行完毕,没有完全初始化;此刻B线程在A进入inited判断为true直接返回,导致只初始化一个defaultJavaValueDecoder的时候返回)

areyouok commented 8 months ago

我知道了,问题是在于register方法