我需要帮助来了解以下代码:
private Predicate composedPredicate = null;
public boolean evaluate(Task taskData) {
boolean isReadLock = false;
try{
rwl.readLock().lock();
isReadLock = true;
if (composedPredicate == null) {
rwl.readLock().unlock();
isReadLock = false;
rwl.writeLock().lock();
if (composedPredicate == null) {
//write to the "composedPredicate" object
}
}
}finally {
if (isReadLock) {
rwl.readLock().unlock();
}else{
rwl.writeLock().unlock();
}
}
return composedPredicate.test(taskData);
}
如果我们在上面的代码中不使用读锁,将会发生什么?
喜欢 :
public boolean evaluate(Task taskData) {
//boolean isReadLock = false;
try{
//rwl.readLock().lock();
//isReadLock = true;
if (composedPredicate == null) {
//rwl.readLock().unlock();
//isReadLock = false;
rwl.writeLock().lock();
if (composedPredicate == null) {
//write to the "composedPredicate" object
}
}
}finally {
rwl.writeLock().unlock();
}
return composedPredicate.test(taskData);
}
仅写数据时,我们真的需要读锁吗?
上面两个代码有什么区别?
我们是否应该甚至使用读取锁来访问对象(composedPredicate)进行null检查?
最佳答案
您发布的第一个代码是Java中使用读/写锁定的双重检查锁定方法的正确实现。
没有读锁的第二种实现方式已损坏。内存模型允许从另一个线程的角度对写入进行重新排序,以查看写入内存的结果。
可能发生的事情是,您可能正在正在读取它的线程中使用未完全初始化的Predicate实例。
您的代码示例:
我们有线程A和B都在运行evaluate
,并且compositionPredicate最初是null
。
答:看到composedPredicate
是null
答:写锁
答:创建Predicate
实现的实例
答:在构造函数中初始化此实例
答:将实例分配给共享变量composedPredicate
答:解锁写锁
B:看到composedPredicate
不是null
B:运行composedPredicate.test(taskData);
但是,系统的编译器,JVM或硬件体系结构对线程A的步骤4和5进行了重新排序,并在初始化共享字段之前为其分配了谓词实例的地址(Java内存模型允许这样做)composedPredicate.test(taskData);
使用未完全初始化的实例运行,并且您的代码在生产中具有随机的意外错误,从而给公司造成巨大损失(可能发生的情况..取决于您所构建的系统)
步骤4和5的重新排序是否发生取决于许多因素。也许只有在系统负载很重的情况下才可以。它可能根本不会在您的OS,硬件,JVM版本等上发生。(但是,在下一版本的JVM,您的OS上,或者当您将应用程序移动到另一台物理计算机上时,它可能突然开始发生)
馊主意。