文档与ReentrantReadWriteLock的矛盾。在公平模式下,最终写锁是否优先于读锁?

时间:2017-03-15 07:33:03

标签: java concurrency locking readwritelock

来自ReentrantLock javadoc:

  

合理模式
  当构建为公平时,线程使用 大致 争夺进入权   到货订单政策。当前举行   锁是释放最长等待的单个写作线程   被分配写锁,或者如果有一组读者线程   等待所有等待的编写器线程,该组将是   分配了读锁定。

     

试图获得公平阅读的线程   如果保持写锁定,则锁定(非重复)将阻塞   有一个等待的作家线程。线程不会获取读取   锁定,直到最旧的当前正在等待的编写器线程   获取并释放写锁。当然,如果是一个等待作家   放弃等待,留下一个或多个读者线程最长   队列中的服务员用写锁定免费,然后那些读者会   被赋予读锁定。

     

尝试获取公平写锁定的线程(非重复)   除非读锁定和写锁定都是空闲的(否则将阻塞)   意味着没有等待的线程)。 (注意非阻塞   ReentrantReadWriteLock.ReadLock.tryLock()和   ReentrantReadWriteLock.WriteLock.tryLock()方法不承认这一点   公平的设置,如果可能的话,将获得锁定,无​​论如何   等待线程。)

也许这是我的英语问题,但我看到这个解释的矛盾:
从第一次paragrapgh我不明白约到货订单政策的含义

  1. 从第一段开始我理解lock获取最旧的等待线程。如果最旧的线程 - 读取线程,那么它将是一组读取线程,其等待时间长于等待最长的写入线程。
  2. 从第二段开始,我了解如果在wait-set中存在写锁定,则不会获取读锁定。
  3. 请澄清这一矛盾。

3 个答案:

答案 0 :(得分:3)

在这里,引用你的引用:

或者如果有的读者线程

换句话说:作家胜过读者;但是当一群读者想要锁定时,那些人会获得锁定。

关于问题:" group 实际意味着" ......这将是一个实施细节,只有通过查看source code才能获得。

答案 1 :(得分:3)

我没有看到你所引用的描述中的任何矛盾,我认为你正确地理解#1但错误地理解#2。

顺便说一下,我认为GhostCat的描述是错误的。没有什么能够总结不同线程的等待时间并对它们进行比较。逻辑实际上要简单得多。

我的回答往往很长,但有希望解释。

非公平模式

让我们从" nonfair"开始先锁定模式。 "非公平"这意味着

  

持续争用的非公平锁可能无限期推迟一个或多个读写器线程

所以"公平"这意味着没有线程可以永远等待。 " Nonfairness"意味着如果有一个恒定的线程流来获取读锁定,并且我们有一些等待写锁定的线程(W1),那么当读取锁定的新线程(Rn)到来时它可能会在W1线程之前锁定,因此可能在不幸的情况下无限期地发生。请注意,即使在" nonfair"模式ReentrantReadWriteLock 尝试是合理公平的,它不是保证公平,因为正如文档所说,"公平"不是免费的,而且成本较低。

非公平模式示例

那么真正的不公平行为可能会如何发生。假设有一个W0线程持有写锁,而现在的队列是R0R1等待读锁,然后W1等待写锁,还有将来会有大量的新线程来读取锁Ri。还假设线程R1线程在系统中具有最低优先级,并且OS永远不会突破线程的优先级,即使它们还没有工作很长时间。

  1. 写锁定由W0保留,等待队列为[R0R1W1]
  2. W0释放写锁定,R0被唤醒并获取读锁定,现在R1具有低优先级且未被唤醒,因此无法获取读取现在锁定。等待队列现在是[R1W1]
  3. W1被唤醒但由于R0
  4. 而无法取得锁定
  5. 现在R0仍然保持读锁定,新读者线程R2到来。由于已经获取了读锁定,并且等待队列中的第一个线程是读取器R1R2立即获取读锁定。读锁定由[R0R2]保存。等待队列仍然是[R1W1]。
  6. 现在R0释放了锁定,但W1仍然无法获得写锁定,因为它现在由R2保留。等待队列仍然是[R1W1]。
  7. 现在R2仍然保持读取锁定,新读者线程R3到达,获取读锁定,同样的故事继续发生。
  8. 重要的是:

    • 第一个写线程W1被读取线程R1阻止读取,由于低优先级和/或纯粹的运气不好而未被唤醒以获取锁定。
    • 对于新到达的Ri线程,找出整个队列中是否有任何写线程需要花费一些时间和精力,因此应用了更简单的启发式(步骤#4):第一个等待线程是否是写入或读取线程,R1正在读取允许快速获取的线程。还要注意,步骤#4中的这个逻辑检查队列中的第一个线程是我前面提到过的公平尝试,这比没有这种检查的天真实现更好。

    合理模式

    现在回到公平。正如您在sources of FairSync内部类中所发现的那样(我剥离了细微的细节):

    class FairSync extends Sync {
         final boolean writerShouldBlock() {
             return hasQueuedPredecessors();
         }
         final boolean readerShouldBlock() {
             return hasQueuedPredecessors();
         }
    }
    

    字面意思是,"公平"和"非公平的"这是"公平"模式读取器线程在获取读取锁之前,否则可以在不破坏ReadWriteLock合约的情况下获取它,另外检查队列中是否还有其他线程。这样,来自前一个示例的W1线程不能永远等待R2,并且下一个线程不会在它之前获取读锁定。

    合理模式示例

    在公平模式中对同一示例的另一次尝试:

    1. 写锁定由W0保留,等待队列为[R0R1W1]
    2. W0释放写锁定,R0获取读锁定队列[R1W1]
    3. W1被唤醒但由于R0
    4. 而无法取得锁定
    5. R2到达队列。尽管读取锁定由R0保持,而R2似乎也能够获取它,但它并不会这样做,因为它会先看到W1。读取锁定由R0保留,队列为[R1W1R2]
    6. 现在,W1R2无法获取锁定,直到R1从队列中删除。因此,最终R1被唤醒,锁定进行处理并释放锁定。
    7. 最后W1获取了写锁定R2R3,其他人仍然在等待队列。
    8. 就此示例而言,R0R1形成"组"但是R2并不属于那个" group"因为它在队列中的W1之后。

      摘要

      所以第一段描述的是当释放锁并且策略很简单时会发生什么:第一个等待的线程获取锁。如果第一个等待线程恰好是读线程,则第一个写入线程在 之前队列中的所有其他读线程 获取读锁定。所有这些读取线程都被称为" group"。请注意,这并不意味着所有读取线程都在等待锁定!

      第二段描述了当新的读取线程到达并尝试获取锁定时发生的情况,此处的行为实际上与第一段一致:如果在当前线程之前队列中存在等待的写入线程,则不会获取如果在释放锁定之前将锁定添加到队列并且第1段中的规则适用,则锁定方式与获取锁定的方式相同。 希望这会有所帮助。

答案 2 :(得分:2)

公平模式政策只是"大约到达顺序"因为等待获取读锁定的线程被批处理,并且稍后到达获取读锁定的线程可能比同一批次中由于OS调度而尝试获取读锁定的另一个线程获得锁定。

A"阅读器线程组"可以只是一个线程。

规范中没有矛盾,但可能没有那么明确。

假设线程A在互斥锁上持有写锁定。

线程B到达并尝试获取写锁定。 然后线程C到达并尝试获取读锁定。然后线程D到达并尝试获取读锁定。然后线程E到达并尝试获得写锁定。然后线程F到达并尝试获取读锁定。

现在,线程A解锁了互斥锁。公平模式策略意味着线程B将获得锁定:它一直在等待最长时间。

当线程B释放锁定时,线程C和D将获得读取锁定,但不会获得线程F.C和D是一组读取器线程,等待的时间超过所有等待的写入器线程"。线程F仍然被阻塞,因为它比编写线程的线程E等待的时间更短。

如果线程E然后放弃等待(例如它超时),则线程F现在是最旧的等待线程,并且当前锁定是读锁定,因此线程F可以在线程C和D释放它们之前获取锁定

如果线程G现在尝试获取写锁定,它将阻塞,直到所有线程C,D和F都释放了它们的读锁定。

如果线程H现在尝试获取读锁定,它将阻止:有一个等待的写入程序线程。

如果线程I现在尝试获取一个写入器锁,它将阻塞:有一个等待线程的队列。

现在,C,D和F释放它们的锁,因此线程G(最长的等待线程)获取写入器锁。

线程G释放锁,线程H获取锁:它是一个读取器线程的组,其等待的时间比任何等待的编写器都长。

最后,当线程H释放锁时,线程我可以获取它。