我在您引用的描述中没有看到任何矛盾,我认为您正确理解了#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
到达。由于已经获取了读锁,并且等待队列中的第一个线程是 reader R1
,R2
因此立即获取读锁。读锁由 [ R0
, R2
] 持有。等待队列仍然是 [ R1
, W1
]。
- 现在
R0
释放锁,但W1
仍然无法获取写锁,因为它现在由R2
. 等待队列仍然是 [ R1
, W1
]。
- 现在,虽然
R2
仍然持有读锁,但新的读线程R3
到达,获取读锁,同样的故事还在继续。
这里重要的是:
- 第一个写入线程被读取线程
W1
阻止读取,该读取线程R1
由于低优先级和/或纯粹的运气不好而没有被唤醒以获取锁。
- 对于新到达的
Ri
线程,要找出整个队列中是否有任何写入线程需要一些时间和精力,因此应用了更简单的启发式方法(步骤#4):第一个等待线程是写入线程还是读取线程,并且R1
正在读取一种允许快速采集。还要注意,在第 4 步检查队列中的第一个线程的这个逻辑是我前面提到的公平的尝试,这比没有这种检查的简单实现要好。
公平模式
所以现在回到公平。正如您在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 段中的规则。希望这可以帮助。