【问题标题】:Documentation contradictions about ReentrantReadWriteLock. Does eventually write lock has priority over read lock or not in fair mode?关于 ReentrantReadWriteLock 的文档矛盾。最终写锁是否优先于读锁或在公平模式下?
【发布时间】:2017-08-05 19:48:23
【问题描述】:

来自ReentrantLockjavadoc:

公平模式
当构造为公平时,线程使用 大约 竞争进入 到货顺序政策。当当前举行 锁被释放,等待时间最长的单个写入线程将 被分配写锁,或者如果有一组读线程 等待比所有等待写入线程更长的时间,该组将是 分配了读锁。

试图获得公平读取的线程 如果持有写锁,锁(不可重入)将阻塞,或者 有一个等待写入线程。线程不会获取读取 锁定直到当前等待的最早的写入线程有 获取并释放写锁。当然,如果一个等待的作家 放弃等待,将一个或多个阅读器线程保留为最长 队列中的服务员没有写锁,那么这些读者将 被分配读锁。

试图获取公平写锁的线程(不可重入) 除非读锁和写锁都是空闲的(这 意味着没有等待线程)。 (注意非阻塞 ReentrantReadWriteLock.ReadLock.tryLock() 和 ReentrantReadWriteLock.WriteLock.tryLock() 方法不遵守这一点 公平设置,如果可能,将获取锁,不管 等待线程。)

也许是我的英语有问题,但我在这个描述中看到了矛盾:
从第一段开始,我不明白大约到达订单政策

的含义
  1. 从第一段我了解到锁获取最旧的等待线程。如果最旧的线程 - 读取线程,那么它将是一组读取线程,其等待时间比等待时间最长的写入线程更长。
  2. 从第二段我了解到,如果等待集中存在写锁,则不会获取读锁。

请澄清这个矛盾。

【问题讨论】:

  • @GhostCat 我只想等待更多答案。你的答案很清楚
  • 这里有趣的是:一小段 javadoc 产生了多少混乱。您可以考虑将您的问题转化为错误报告,以便将来的用户在使用这些锁时更轻松:-)
  • @GhostCat 这真是一件奇怪的事情。我认为文档应该更友好
  • 这就是为什么我认为这值得在一些 bugtracker 上输入。

标签: java concurrency locking readwritelock


【解决方案1】:

在这里,引用您的报价:

或者如果有 读者线程

换句话说:作家战胜了单一读者;但是当一群读者想要锁时,那些人会得到锁。

关于这个问题:“group 到底是什么意思”......这将是一个实现细节,只能通过查看source code 获得。

【讨论】:

  • 从哪个尺寸开始分组?
【解决方案2】:

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

顺便说一句,我认为 GhostCat 的描述是错误的。没有什么可以总结不同线程的等待时间并进行比较。逻辑其实要简单得多。

我的回答往往很长,但希望能解释清楚。

不公平模式

让我们先从“非公平”模式锁定开始。这里的“不公平”是指

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

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

非公平模式示例

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

  1. 写锁由W0持有,等待队列为[R0,R1,W1]
  2. W0 释放写锁,R0 被唤醒并获取读锁,现在R1 优先级低,没有被唤醒,所以现在无法获取读锁。等待队列现在是 [R1, W1]
  3. W1 被唤醒但由于R0 而无法获取锁
  4. 现在R0 仍然持有读锁,新的读线程R2 到达。由于已经获得读锁并且等待队列中的第一个线程是读卡器R1R2 立即获得读锁。读锁由 [R0, R2] 持有。等待队列仍然是 [R1, W1]。
  5. 现在R0 释放了锁,但W1 仍然无法获取写锁,因为它现在由R2 持有。等待队列仍然是 [R1, W1]。
  6. 现在虽然R2 仍然持有读锁,但新的读线程R3 到达,获取了读锁,同样的故事还在继续。

这里重要的是:

  • 第一个写入线程 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持有,等待队列为[R0,R1,W1]
  2. W0释放写锁,R0获取读锁队列为[R1,W1]
  3. W1 被唤醒但由于R0 而无法获取锁
  4. R2 到达队列。尽管读锁由R0 持有,R2 似乎也能够获取它,但它并没有这样做,因为它先于自己看到W1。读锁由R0持有,队列为[R1,W1,R2]
  5. 现在W1R2 在从队列中删除R1 之前都无法获取锁。因此最终R1被唤醒得到锁做处理并释放锁。
  6. 最后W1获得写锁,R2R3等还在队列中等待。

就本例而言,R0R1 形成一个“组”,但 R2 不属于该“组”,因为它在队列中的 W1 之后。

总结

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

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

【讨论】:

  • @gstackoverflow,我不明白你所说的“关于非公平模式的第二段”。在您链接的规范中,只有一个段落的两个句子。你可以再详细一点吗?至于“非公平模式下的队列”,这是库设计者做出的权衡:他们认为,即使在“非公平”模式,因此“非公平”模式锁定对于大多数用户来说可能是相当安全的默认选择(查看默认构造函数的作用)。
  • 对不起,是我的错
  • @gstackoverflow,现在一切都清楚了吗?还是有更多问题?
  • 现在你的答案很清楚了。我正在等待社区反应
  • 出于好奇:您是否研究了实现,或者您是如何制定解释的?尽管您敢于纠正我:p1 为您提供出色的意见。
【解决方案3】:

公平模式策略只是“近似到达顺序”,因为等待获取读锁的线程是成批的,而稍后到达以获取读锁的线程可能比同一批中的另一个线程更早获得锁由于操作系统调度,试图获取读锁。

“一组读者线程”可以只是一个线程。

规范中没有矛盾,但可能不够清晰。

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

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

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

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

如果线程E然后放弃等待(例如它超时),那么线程F现在是最旧的等待线程,并且当前锁是读锁,所以线程F可以在线程C和D释放他们的锁之前获得锁.

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-30
    • 2014-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多