【问题标题】:Is there any WriteLock acquire priority over ReadLock in ReentrantReadWriteLockReentrantReadWriteLock 中是否有任何 WriteLock 优先于 ReadLock
【发布时间】:2019-10-15 13:29:58
【问题描述】:

我已阅读 java 文档:ReentrantReadWriteLock

而且我没有看到 writeLock 比 readLock 有任何优先级

但我也读过这样的主题:Are read and write locks in ReentrantReadWriteLock somehow related?
我在两个答案中都看到了以下短语:

如果锁被读者持有并且线程不再请求写锁 允许读者获取读锁,直到线程拥有 获得的写锁释放吧。


更喜欢作者而不是读者。也就是说,如果作家正在等待 锁,不允许其他线程的新读者访问 资源。现有读者可以继续使用该资源,直到他们 释放锁。这可以防止所谓的“作家饥饿”。

这些短语听起来很有意义,看起来我已经在其他地方读过。

但显然和java doc是矛盾的。

在最新的 JDK 中是否发生了变化?还有效吗?

如何防止作家饿死?

【问题讨论】:

    标签: java multithreading concurrency reentrantreadwritelock


    【解决方案1】:

    ReentrantReadWriteLock 描述了两种操作模式:公平模式和非公平模式。您引用的选择似乎旨在描述公平模式。我不会说两者显然是矛盾的。但是,我可以看到第一个中的不精确措辞(“不允许更多读者”)可能会导致混淆。我怀疑“更多读者”是指来自其他线程的新读者,而不是来自同一线程的额外重入阅读,有些人可能会将其解释为同一个读者。如果以这种方式解释,那么它们似乎与 JavaDoc 一致。

    【讨论】:

    • 来自 java doc:当构造为公平时,线程使用近似到达顺序策略竞争进入。当当前持有的锁被释放时,要么为等待时间最长的单个写入线程分配写入锁,要么如果有一组读取线程等待的时间比所有等待写入线程的时间长,则为该组分配读取锁 我在这里看不到写线程的任何优先级
    • 感谢您的解释。如果专注于“优先于读者而不是读者”,那么我同意这与 JavaDoc 不一致,JavaDoc 说,“此类不会为锁定访问强加读者或作家的偏好顺序”。他们对随后的实际行为的描述似乎很好。关于防止写入器饥饿,给予写入器线程优先级将是一种方法,但是 ReentrantReadWriteLock 的基于到达的公平模式如何不也是一种方法呢?在写入请求之后请求的不可重入读取将需要等待写入获得并释放锁。
    • 这里要指出的一个技术细节是示例代码实际上并不能保证尝试锁定的顺序。虽然实际上说,线程以该顺序启动,但通常会以这种方式发生。您观察到的行为与 JavaDoc 对公平模式的描述一致:“最长等待的单个写入线程将被分配写入锁,或者如果有一组读取线程等待的时间比所有等待写入线程的时间长,那么组将被分配读锁。” (已达到字符限制,在下一条评论中继续...)
    • 这里令人惊讶的是,您没有启用公平模式,但仍然观察到了这种行为。这里要指出的一件事是,JavaDoc 中没有任何内容可以阻止 JDK 的 ReentrantReadWriteLock 实现通过重用其公平模式实现来实现非公平模式。非公平模式只是未指定/未记录的订单。如果 JDK 正在这样做,我会警告不要依赖这样的行为。如果这是您需要的行为,请始终明确启用公平模式。
    【解决方案2】:

    我已经创建了要检查的示例:

    public class RWLockTest {
        public static final Logger LOGGER = LoggerFactory.getLogger(RWLockTest.class);
    
           public static void main(String[] args) {
            SomeClass someClass = new SomeClass();
    
            Reader readerRunnable = new Reader(someClass);
            Writer writerRunnable = new Writer(someClass);
            //group 1 readers
            for (int i = 0; i < 10; i++) {
                new Thread(readerRunnable).start();
            }
    
            // 2 writers
            new Thread(writerRunnable).start();
            LOGGER.info("!!!!!!!!!!!!!!!WRITER_1 WAS STARTED!!!!!!!!!!!!!!!");
            new Thread(writerRunnable).start();
            LOGGER.info("!!!!!!!!!!!!!!!WRITER_2 WAS STARTED!!!!!!!!!!!!!!!");
    
           //group 2 readers            
           for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(readerRunnable);
                LOGGER.info(String.format("%s was submitted", thread.getId()));
                thread.start();
            }
        }
    
        public static class SomeClass {
            public ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
            public void read() {
                readWriteLock.readLock().lock();
                try {
                    LOGGER.info(String.format("Read by %s started", Thread.currentThread().getId()));
                    Thread.sleep(5000);
                    LOGGER.info(String.format("Read by %s finished", Thread.currentThread().getId()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    readWriteLock.readLock().unlock();
    
                }
            }
    
            public void write() {
                readWriteLock.writeLock().lock();
                try {
                    LOGGER.info(String.format("!!!!!!!!!!Write by %s started!!!!!!!!!!!!", Thread.currentThread().getId()));
                    Thread.sleep(3000);
                    LOGGER.info(String.format("!!!!!!!!!!Write by %s finished!!!!!!!!", Thread.currentThread().getId()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    readWriteLock.writeLock().unlock();
    
                }
            }
    
        }
    
        public static class Reader implements Runnable {
            SomeClass someClass;
    
            public Reader(SomeClass someClass) {
                this.someClass = someClass;
            }
    
            @Override
            public void run() {
                someClass.read();
            }
        }
    
        public static class Writer implements Runnable {
            SomeClass someClass;
    
            public Writer(SomeClass someClass) {
                this.someClass = someClass;
            }
    
            @Override
            public void run() {
                someClass.write();
            }
        }
    }
    

    我尝试运行了很多时间,输出通常是这样的:

    16:31:49.037 [main] INFO my.pack.RWLockTest - !!!!!!!!!!!!!!!WRITER_1 WAS STARTED!!!!!!!!!!!!!!!
    16:31:49.040 [main] INFO my.pack.RWLockTest - !!!!!!!!!!!!!!!WRITER_2 WAS STARTED!!!!!!!!!!!!!!!
    16:31:49.046 [Thread-1] INFO my.pack.RWLockTest - Read by 13 started
    16:31:49.046 [main] INFO my.pack.RWLockTest - 24 was submitted
    16:31:49.046 [Thread-7] INFO my.pack.RWLockTest - Read by 19 started
    16:31:49.046 [Thread-4] INFO my.pack.RWLockTest - Read by 16 started
    16:31:49.046 [Thread-5] INFO my.pack.RWLockTest - Read by 17 started
    16:31:49.046 [Thread-0] INFO my.pack.RWLockTest - Read by 12 started
    16:31:49.046 [Thread-3] INFO my.pack.RWLockTest - Read by 15 started
    16:31:49.047 [main] INFO my.pack.RWLockTest - 25 was submitted
    16:31:49.046 [Thread-9] INFO my.pack.RWLockTest - Read by 21 started
    16:31:49.047 [Thread-8] INFO my.pack.RWLockTest - Read by 20 started
    16:31:49.047 [main] INFO my.pack.RWLockTest - 26 was submitted
    16:31:49.047 [Thread-2] INFO my.pack.RWLockTest - Read by 14 started
    16:31:49.047 [Thread-6] INFO my.pack.RWLockTest - Read by 18 started
    16:31:49.047 [main] INFO my.pack.RWLockTest - 27 was submitted
    16:31:49.048 [main] INFO my.pack.RWLockTest - 28 was submitted
    16:31:49.048 [main] INFO my.pack.RWLockTest - 29 was submitted
    16:31:49.048 [main] INFO my.pack.RWLockTest - 30 was submitted
    16:31:49.048 [main] INFO my.pack.RWLockTest - 31 was submitted
    16:31:49.049 [main] INFO my.pack.RWLockTest - 32 was submitted
    16:31:49.049 [main] INFO my.pack.RWLockTest - 33 was submitted
    16:31:54.047 [Thread-7] INFO my.pack.RWLockTest - Read by 19 finished
    16:31:54.048 [Thread-6] INFO my.pack.RWLockTest - Read by 18 finished
    16:31:54.047 [Thread-5] INFO my.pack.RWLockTest - Read by 17 finished
    16:31:54.049 [Thread-2] INFO my.pack.RWLockTest - Read by 14 finished
    16:31:54.051 [Thread-8] INFO my.pack.RWLockTest - Read by 20 finished
    16:31:54.047 [Thread-1] INFO my.pack.RWLockTest - Read by 13 finished
    16:31:54.050 [Thread-9] INFO my.pack.RWLockTest - Read by 21 finished
    16:31:54.049 [Thread-4] INFO my.pack.RWLockTest - Read by 16 finished
    16:31:54.049 [Thread-3] INFO my.pack.RWLockTest - Read by 15 finished
    16:31:54.049 [Thread-0] INFO my.pack.RWLockTest - Read by 12 finished
    16:31:54.057 [Thread-10] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 22 started!!!!!!!!!!!!
    16:31:57.057 [Thread-10] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 22 finished!!!!!!!!
    16:31:57.058 [Thread-11] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 23 started!!!!!!!!!!!!
    16:32:00.060 [Thread-11] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 23 finished!!!!!!!!
    16:32:00.061 [Thread-13] INFO my.pack.RWLockTest - Read by 25 started
    16:32:00.061 [Thread-14] INFO my.pack.RWLockTest - Read by 26 started
    16:32:00.061 [Thread-12] INFO my.pack.RWLockTest - Read by 24 started
    16:32:00.061 [Thread-15] INFO my.pack.RWLockTest - Read by 27 started
    16:32:00.061 [Thread-17] INFO my.pack.RWLockTest - Read by 29 started
    16:32:00.062 [Thread-19] INFO my.pack.RWLockTest - Read by 31 started
    16:32:00.062 [Thread-18] INFO my.pack.RWLockTest - Read by 30 started
    16:32:00.061 [Thread-16] INFO my.pack.RWLockTest - Read by 28 started
    16:32:00.062 [Thread-20] INFO my.pack.RWLockTest - Read by 32 started
    16:32:00.062 [Thread-21] INFO my.pack.RWLockTest - Read by 33 started
    16:32:05.060 [Thread-12] INFO my.pack.RWLockTest - Read by 24 finished
    16:32:05.060 [Thread-15] INFO my.pack.RWLockTest - Read by 27 finished
    16:32:05.060 [Thread-13] INFO my.pack.RWLockTest - Read by 25 finished
    16:32:05.060 [Thread-17] INFO my.pack.RWLockTest - Read by 29 finished
    16:32:05.060 [Thread-14] INFO my.pack.RWLockTest - Read by 26 finished
    16:32:05.062 [Thread-21] INFO my.pack.RWLockTest - Read by 33 finished
    16:32:05.062 [Thread-16] INFO my.pack.RWLockTest - Read by 28 finished
    16:32:05.062 [Thread-19] INFO my.pack.RWLockTest - Read by 31 finished
    16:32:05.062 [Thread-18] INFO my.pack.RWLockTest - Read by 30 finished
    16:32:05.062 [Thread-20] INFO my.pack.RWLockTest - Read by 32 finished
    

    这是什么意思?

    如您所见,我执行以下操作:

    1. 运行 10 个线程进行读取。所有 10 个线程都可以并行工作。每次读取需要 5 秒
    2. 然后我启动 2 个写入器(1 个写入需要 3 秒)
    3. 那我开始10个读者

    如您所见,提交第一次写入 (16:31:49.037 [main] INFO my.pack.RWLockTest - !!!!!!!!!!!!!!!WRITER_1 WAS STARTED!!!!!!!!!!!!!!!) 和实际声明时 ((acquired lock)16:31:54.057 [Thread-10] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 22 started!!!!!!!!!!!!) 之间的延迟为 5 秒。现在是阅读时间。

    您还可以看到,在提交了 2 个 writer 线程后,我们提交了 10 个 ids 从 24 到 33 的 reader 线程,并且它们实际上都是在 writer 线程完成工作后启动(获取锁)。

    因此,ID 为 从 24 到 33 的读者线程在 16:31:49 提交,此时前 10 个读者获得了 readlock,看起来他们也能够获得 readlock,但看起来像那里是ReentrantReadWriteLock 内部的东西,可以防止它避免作家饥饿。最终第二组读者只能在16:32:00(提交后6秒)获得锁

    我不知道它是否有保证,但从我的测试来看,它总是以这种方式工作。 所以我们的作者优先于读者,尽管java doc 说:

    这个类没有强加读者或作者的偏好排序 锁定访问

    【讨论】:

      【解决方案3】:

      确定的答案总是在代码中,所以让我们看看那里。

      这里是构造函数(注意:默认构造函数使用fair 设置false 调用这个)

      public ReentrantReadWriteLock(boolean fair) {
          sync = fair ? new FairSync() : new NonfairSync();
          readerLock = new ReadLock(this);
          writerLock = new WriteLock(this);
      }
      

      因此,唯一的区别是sync 属性是包含FairSync 实例还是NonfairSync 实例。这些实现有何不同?

      这是来自FairSync 类的writerShouldBlock 方法的代码:

      final boolean writerShouldBlock() {
          return hasQueuedPredecessors();
      }
      

      这意味着“如果有一行”,那么作者会阻塞并进入该行(队列)。然而,这与NonfairSync 类的实现形成鲜明对比,后者是:

      final boolean writerShouldBlock() {
          return false;
      }
      

      这明确显示了non fair mode 的作者如何获得高于读者的优先权。

      关于作家饥饿的最后评论。在non fair mode 中,这是在伴随方法的实现中实现的:readerShouldBlockNonfairSync 类中的代码中的 cmets 声明:

          final boolean readerShouldBlock() {
              /* As a heuristic to avoid indefinite writer starvation,
               * block if the thread that momentarily appears to be head
               * of queue, if one exists, is a waiting writer.  This is
               * only a probabilistic effect since a new reader will not
               * block if there is a waiting writer behind other enabled
               * readers that have not yet drained from the queue.
               */
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-05-29
        • 1970-01-01
        • 1970-01-01
        • 2012-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-05
        相关资源
        最近更新 更多