Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zhaoguozhenjava opened this issue Jan 16, 2024 · 11 comments

Comments

@zhaoguozhenjava
Copy link

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

@areyouok
Copy link
Collaborator

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

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

@zhaoguozhenjava
Copy link
Author

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

@areyouok
Copy link
Collaborator

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

@zhaoguozhenjava
Copy link
Author

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

@areyouok
Copy link
Collaborator

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

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

@zhaoguozhenjava
Copy link
Author

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

@zhaoguozhenjava
Copy link
Author

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

@areyouok
Copy link
Collaborator

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

@areyouok
Copy link
Collaborator

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

@zhaoguozhenjava
Copy link
Author

zhaoguozhenjava commented Jan 20, 2024

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
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants