简体   繁体   English

写时读锁

[英]Read lock while Writing

I need help in understanding the below code :我需要帮助来理解以下代码:

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);
    }

What will happen if we don't use Read Locks in the above code?如果我们在上面的代码中不使用 Read Locks 会发生什么? Like :喜欢:

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);
    }
  1. Do we really need Read locks while we are only writing the data?当我们只写数据时,我们真的需要读锁吗?
  2. What is the difference between the above two codes?上面两个代码有什么区别?
  3. Should we use Read locks even for accessing the object(composedPredicate) for null check?我们是否应该使用读取锁来访问对象(composedPredicate)进行空检查?

The first code that you posted is a correct implementation of the double-checked locking approach in Java using a read/write lock.您发布的第一个代码是使用读/写锁在 Java 中正确实现双重检查锁定方法。

Your second implementation without a read-lock is broken.你没有读锁的第二个实现被破坏了。 The memory model allows writes to be reordering from the perspective of another thread seeing the result of the writes to memory.内存模型允许从另一个线程查看写入内存的结果的角度重新排序写入。

What could happen is that you could be using a not-fully initialized instance of Predicate in the thread that is reading it.可能发生的情况是,您可能在读取它的线程中使用了未完全初始化的 Predicate 实例。

Example with your code:您的代码示例:

We have thread A and B both running evaluate and composedPredicate is null initially.我们有线程 A 和 B 都在运行evaluate并且null最初为null

  1. A: sees composedPredicate is null A:看到composedPredicatenull
  2. A: write-locks A:写锁
  3. A: creates an instance of an implementation of Predicate A: 创建Predicate实现的实例
  4. A: initializes this instance in the constructor A:在构造函数中初始化这个实例
  5. A: assigns the instance to the the shared variable composedPredicate A:将实例分配给共享变量composedPredicate
  6. A: unlocks the write lock A:解锁写锁
  1. B: sees composedPredicate is not null B:看到composedPredicate不为null
  2. B: runs composedPredicate.test(taskData); B: 运行composedPredicate.test(taskData);
  3. HOWEVER, the compiler, the JVM, or the hardware architecture of your system reordered steps 4 and 5 of thread A, and assigned the address of the Predicate instance of the shared field before it was initialized (this is allowed by the Java Memory model)然而,编译器、JVM 或系统的硬件架构重新排序了线程 A 的第 4 步和第 5 步,并在它被初始化之前分配了共享字段的 Predicate 实例的地址(这是 Java 内存模型允许的)
  4. composedPredicate.test(taskData); is run using a not-fully initialized instance and your code has random unexpected errors in production resulting in great losses to your company (potentially that happens .. depends on the system that you're building)使用未完全初始化的实例运行,并且您的代码在生产中出现随机意外错误,从而给您的公司造成巨大损失(可能发生这种情况……取决于您正在构建的系统)

Whether or not the reordering of step 4 and 5 happens depends on many factors.步骤 4 和 5 的重新排序是否发生取决于许多因素。 Maybe it only does under heavy system load.也许它只在系统负载很重的情况下才有效。 It may not happen at all on your OS, hardware, version of JVM, etc. (But on the next version of the JVM, your OS, or when you move your application to a different physical machine, it may suddenly start happening)它可能在您的操作系统、硬件、JVM 版本等上根本不会发生(但在 JVM 的下一个版本、您的操作系统上,或者当您将应用程序移动到不同的物理机器时,它可能会突然开始发生)

Bad idea.坏主意。

This code is simular to an old 'Singleton-pattern' wich makes use of the synchronozed blocks.此代码类似于使用同步块的旧“单例模式”。 Eg例如

class Singleton
{
    volatile Singleton s;

    public static Singleton instance()
    {
        if(s == null)
        {
             synchronized(Singleton.class)
             {
                  if(s == null)
                      s = new Singleton();
             }
         }
         return s;
    }
}

Notice the double 'null-check' where only the second one is synchronozed.注意双重“空检查”,其中只有第二个是同步的。 The reason for doing the first 'null-check' is to prevent the blocking of threads if the instance() method is called (because when not null, it can proceed without synchronization).进行第一次“空检查”的原因是为了防止在调用instance()方法时阻塞线程(因为当不为空时,它可以在没有同步的情况下继续进行)。

Your first code is doing the same.你的第一个代码也在做同样的事情。 First it checks if there is something assigned to composedPredicate .首先它检查是否有分配给composedPredicate东西。 And if that isnt the case, only than will it aquire a writingLock (wich blocks all other Thread oposed to readLocks, which only blocks writeLocks).如果不是这种情况,那么它只会获得一个writingLock(它会阻止所有其他与 readLocks 相对的线程,而 readLocks 只会阻止 writeLocks)。

The main difference with the 'singleton-pattern' and your code is that your value can be reassignes. 'singleton-pattern' 和您的代码的主要区别在于您的值可以重新分配。 This can only happen safly if it makes sure nobody is reading the value during modification.这只有在确保没有人在修改期间读取值时才能安全地发生。 By removing the readLock you basically create a possibility that a thread may get undefined results (if not a crash) when accessing the composedPredicate while another Thread is modifying that same field.通过消除readLock你基本上创建一种可能性,即访问当一个线程可能会得到不确定的结果(如果不是崩溃) composedPredicate而另一个线程正在修改的是同一领域。

So to answer your questions: 1. You dont need a readLock for writing, only a writeLock (wich will block all other Threads whonare trying to lock).所以回答你的问题: 1. 你不需要一个 readLock 来写,只需要一个 writeLock (这将阻止所有其他试图锁定的线程)。 But in this design-pattern you cannot leave it out.但在这种设计模式中,你不能忽略它。 2. & 3. See explanation above. 2. & 3. 见上面的解释。

Hope this was enough to get a grasp of this pattern.希望这足以掌握这种模式。

EDIT As commented by Erwin Bolwidt , the above pattern is considered broken (without the 'volatile' keyword) due to compiler/CPU code optimization (where read/write actions may happen out of order).编辑正如 Erwin Bolwidt 所评论的那样,由于编译器/CPU 代码优化(读/写操作可能无序发生),上述模式被认为是损坏的(没有 'volatile' 关键字)。 In the linked blog there are examples for alternatives/fixes for this problem.在链接的博客中有此问题的替代方案/修复示例。 It turns out the 'volatile' keyword creates a barier which disallows reordering of read and write operations by either the compiler or CPU optimization, and thus 'fixes' the 'double-checked-locking' pattern described above.事实证明,'volatile' 关键字创建了一个屏障,它不允许编译器或 CPU 优化对读写操作进行重新排序,从而“修复”了上述的“双重检查锁定”模式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM