我在您引用的描述中没有看到任何矛盾,我认为您正确理解了#1,但错误地理解了#2。
顺便说一句,我认为 GhostCat 的描述是错误的。没有什么可以总结不同线程的等待时间并进行比较。逻辑其实要简单得多。
我的回答往往很长,但希望能解释清楚。
不公平模式
让我们先从“非公平”模式锁定开始。这里的“不公平”是指
持续竞争的非公平锁可能会无限期推迟一个或多个读取器或写入器线程
所以这里的“公平”意味着没有线程可以永远等待。 “不公平”意味着如果有一个恒定的线程流来获取读锁,并且我们有一些线程(W1)正在等待写锁,当新的读锁线程(Rn)来的时候可能会在W1 线程之前获得锁定,因此在不幸的情况下可能会无限期地发生。请注意,即使在“不公平”模式ReentrantReadWriteLock 尝试合理公平,它也不能保证公平,因为正如文档所说,“公平”不是免费的并且成本是较低的吞吐量。
非公平模式示例
那么真正的不公平行为是如何发生的。假设有一个W0线程持有写锁,队列现在是R0和R1在等待读锁,然后W1在等待写锁,而且未来还会有一个巨大的读取锁Ri的新线程流。还假设线程R1线程在系统中具有最低的优先级,并且操作系统不会提高线程的优先级,即使它们很长时间没有工作。
- 写锁由
W0持有,等待队列为[R0,R1,W1]
-
W0 释放写锁,R0 被唤醒并获取读锁,现在R1 优先级低,没有被唤醒,所以现在无法获取读锁。等待队列现在是 [R1, W1]
-
W1 被唤醒但由于R0 而无法获取锁
- 现在
R0 仍然持有读锁,新的读线程R2 到达。由于已经获得读锁并且等待队列中的第一个线程是读卡器R1,R2 立即获得读锁。读锁由 [R0, R2] 持有。等待队列仍然是 [R1, W1]。
- 现在
R0 释放了锁,但W1 仍然无法获取写锁,因为它现在由R2 持有。等待队列仍然是 [R1, W1]。
- 现在虽然
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 那样永远等待,并且下一个线程不会在它之前获得读锁。
公平模式示例
在公平模式下对同一示例的另一次尝试:
- 写锁由
W0持有,等待队列为[R0,R1,W1]
-
W0释放写锁,R0获取读锁队列为[R1,W1]
-
W1 被唤醒但由于R0 而无法获取锁
-
R2 到达队列。尽管读锁由R0 持有,R2 似乎也能够获取它,但它并没有这样做,因为它先于自己看到W1。读锁由R0持有,队列为[R1,W1,R2]
- 现在
W1 和R2 在从队列中删除R1 之前都无法获取锁。因此最终R1被唤醒得到锁做处理并释放锁。
- 最后
W1获得写锁,R2、R3等还在队列中等待。
就本例而言,R0 和 R1 形成一个“组”,但 R2 不属于该“组”,因为它在队列中的 W1 之后。
总结
所以第一段描述的是当一个锁被释放时会发生什么并且策略很简单:第一个等待线程获取锁。如果第一个等待线程恰好是读线程,则队列中的所有其他读线程在第一个写线程之前获取读锁。所有此类读取线程都称为“组”。请注意,这并不意味着所有读取线程都在等待锁定!
第二段描述了当一个新的读线程到达并尝试获取锁时会发生什么,这里的行为实际上与第一段一致:如果队列中在当前线程之前有一个等待的写线程,它将不会获取如果在锁被释放之前将锁添加到队列中,那么它不会获取锁的方式与第 1 段中的规则将适用的方式相同。
希望这会有所帮助。