10

来自ReentrantLock javadoc:

公平模式
当构建为公平模式时,线程使用近似 到达顺序策略来竞争进入。当当前持有的锁被释放时,等待时间最长的单个写入线程将被分配写入锁,或者如果有一组读取线程等待的时间比所有等待写入线程的时间长,则将为该组分配读取锁。

如果持有写锁或存在等待写入线程,则尝试获取公平读锁(不可重入)的线程将阻塞。直到当前等待的最早的写线程获得并释放写锁之后,该线程才会获得读锁。当然,如果一个等待的写入者放弃它的等待,留下一个或多个读取线程作为队列中最长的等待者,并且没有写入锁,那么这些读取器将被分配读取锁。

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

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

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

请澄清这个矛盾。

4

3 回答 3

2

在这里,引用您的报价:

或者如果有一读者线程

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

关于这个问题:“组实际上是什么意思”......这将是一个实现细节,只能通过查看源代码获得。

于 2017-03-15T07:36:03.053 回答
2

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

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

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

非公平模式

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

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

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

非公平模式示例

那么真正的不公平行为是如何发生的。假设有一个W0线程持有写锁和队列,目前正在等待读锁,然后R0等待写锁,并且将来会有大量新线程进入读锁。还假设线程线程在系统中具有最低的优先级,并且操作系统不会提高线程的优先级,即使它们已经很长时间没有工作了。R1W1RiR1

  1. 写锁由 持有W0,等待队列为 [ R0, R1, W1]
  2. W0释放写锁,R0被唤醒并获得读锁,现在R1优先级低,没有被唤醒,所以现在不能获得读锁。等待队列现在是 [ R1, W1]
  3. W1被唤醒但无法获取锁,因为R0
  4. 现在虽然R0仍然持有读锁,但新的读取器线程R2到达。由于已经获取了读锁,并且等待队列中的第一个线程是 reader R1R2因此立即获取读锁。读锁由 [ R0, R2] 持有。等待队列仍然是 [ R1, W1]。
  5. 现在R0释放锁,但W1仍然无法获取写锁,因为它现在由R2. 等待队列仍然是 [ R1, W1]。
  6. 现在,虽然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下一个线程不会在它之前获得读锁。

公平模式示例

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

  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 段中的规则。希望这可以帮助。

于 2017-03-17T20:05:20.497 回答
1

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

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

规范中没有矛盾,但它可能并不像它可能的那样清楚。

假设线程 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 可以获取它。

于 2017-03-21T09:59:49.527 回答