Java 写时读锁
我需要帮助理解以下代码:Java 写时读锁,java,readwritelock,reentrantreadwritelock,Java,Readwritelock,Reentrantreadwritelock,我需要帮助理解以下代码: private Predicate composedPredicate = null; public boolean evaluate(Task taskData) { boolean isReadLock = false; try{ rwl.readLock().lock(); isReadLock = true; if (composedPredicate ==
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);
}
如果在上述代码中不使用读锁,会发生什么?
比如:
您发布的第一个代码是Java中使用读/写锁的双重检查锁定方法的正确实现 没有读锁的第二个实现被破坏。内存模型允许从另一个线程的角度对写入进行重新排序,以查看写入内存的结果 可能发生的情况是,您可能正在读取谓词的线程中使用未完全初始化的谓词实例 代码示例: 我们让线程A和B都运行
evaluate
,并且composedPredicate最初为null
composedPredicate
isnull
谓词
composedPredicate
composedPredicate
是不是null
composedPredicate.test(taskData)代码>
composedPredicate.test(taskData)
是使用未完全初始化的实例运行的,并且您的代码在生产过程中随机出现意外错误,从而给您的公司造成巨大损失(可能会发生这种情况..取决于您正在构建的系统)坏主意。此代码类似于旧的“单例模式”,它使用同步块。例如
class Singleton
{
volatile Singleton s;
public static Singleton instance()
{
if(s == null)
{
synchronized(Singleton.class)
{
if(s == null)
s = new Singleton();
}
}
return s;
}
}
注意双“空检查”,其中只有第二个同步。执行第一个“null检查”的原因是为了防止在调用instance()
方法时阻塞线程(因为当非null时,它可以在不同步的情况下继续)
您的第一个代码也在这样做。首先,它检查是否有分配给composedPredicate
的内容。如果不是这样的话,它只会获得一个写锁(它会阻止所有其他读锁线程,而读锁只会阻止写锁)
“单例模式”与代码的主要区别在于可以重新分配值。只有在确保修改期间没有人读取值时,才能安全地执行此操作。通过删除readLock,您基本上创建了一种可能性,即当另一个线程修改同一字段时,当访问composedPredicate
时,一个线程可能会得到未定义的结果(如果不是崩溃的话)
因此,要回答您的问题:
1.写入时不需要读锁,只需要写锁(它将阻止所有其他试图锁定的线程)。但在这种设计模式中,您不能忽略它。
2. & 3.见上面的解释
希望这足以掌握这种模式
编辑
正如Erwin Bolwidt所评论的,由于编译器/CPU代码优化(读/写操作可能发生无序),上述模式被认为是破坏的(没有'volatile'关键字)。在链接的博客中,有关于此问题的替代方案/修复方案的示例。事实证明,“volatile”关键字创建了一个barier,它不允许编译器或CPU优化对读写操作进行重新排序,从而“修复”了上述“双重检查锁定”模式。您在答案中发布的单例的双重检查锁定被破坏。它的问题与OP发布的第二个代码示例(没有读锁)的问题相同(最终可能会在线程中使用未初始化或部分初始化的单例,该单例只进行读取而不进行同步)。读这篇文章:啊,很有趣。我知道它被认为是“糟糕的设计”,但我不知道它坏了。这就是你每天学习的方式。谢谢你的提示!谢谢你的解释!还有一个问题是:假设在我给出的第二个代码中,如果我们在第二次空检查后使用函数获取初始化对象,我们需要读锁吗?因为,对象将在函数调用执行后返回。例如:第二次空检查:如果(composedPredicate==null){composedPredicate=getInitialisedObject();}编译器、JVM和硬件体系结构仍然可以对对象的初始化和对引用的写入重新排序,甚至可以跨越方法边界。唯一能阻止这种(潜在的)重新排序的是在中定义的“发生在之前”关系。建立这种关系的典型方法是
同步
,易失性
,以及java.util.concurrent
中许多类中的方法。
class Singleton
{
volatile Singleton s;
public static Singleton instance()
{
if(s == null)
{
synchronized(Singleton.class)
{
if(s == null)
s = new Singleton();
}
}
return s;
}
}