为什么Java同时拥有CountDownLatch和CyclicBarrier?

4

我正在阅读多线程,并遇到了这两个多线程概念,我已经了解它们的区别以及应该在什么情况下使用它们,但我不明白为什么Java有两个类可以完成同样的工作?

CyclicBarrier可以做CountDownLatch的所有事情,那么为什么CountDownLatch会出现在Java标准库中呢?


有时候类之间的功能可能会重叠。这并不意味着所有潜在可以被另一个类替换的类都是不必要的。 - Kayaman
这就是问题所在,有哪些功能可能被覆盖?我需要在这种特殊情况下了解。但无论我阅读哪里,都只得到一个答案,即一个可重用,另一个不可重用,这就是唯一的区别。我宁愿始终使用可重用,即使我目前没有重用,那么为什么Java有两个类呢?问题仍未得到解答--谢谢回复 - Vj More
在需要单次功能的情况下,为什么会使用可重复使用的'CyclicBarrier'呢?这可能导致一个错误! - Kayaman
为什么会出现哪些类型的错误? - Vj More
你会因为没有使用正确的工具而导致的错误。 - Kayaman
5个回答

7

一个简短、不过于详细的答案...首先附上它们各自的Javadoc链接:

简而言之,主要区别在于,与CyclicBarrier不同的是,一旦CountDownLatch完成且结束,它就不能被重复使用。Javadoc明确提到了这一点:

这是一个一次性现象 -- 计数无法被重置。如果需要一个可以重置计数的版本,请考虑使用CyclicBarrier。

确实如此,我们发现CyclicBarrier有一个名为.reset()的方法,它做了它的意思。不仅如此,还有一个构造函数的版本将一个Runnable关联到每次触发障碍时运行(这就是Javadoc所说的,别问我)。

因此,它们确实不同,简单地说,一个是可重用的(CyclicBarrier),而另一个是不可重用的(CountDownLatch)。


2
如果我的问题之前不够清晰,我很抱歉,请让我重新表述一下。我已经阅读了很多关于这两个类的区别的帖子。我的问题是,我们可以使用CyclicBarrier来完成CountDownLatch所能做的一切,而且没有额外的开销,那么为什么Java会提供这两个单独的类呢? - Vj More
你没有正确阅读我的答案。再说一遍:你不能重复使用 CountDownLatch;你可以重复使用 CyclicBarrier。不,一个不能代替另一个。 - fge
3
我知道CountDownLatch不可重用,而CyclicBarrier是可重用的,这一点毫无疑问,很明显。让我这样说,如果我可以使用CyclicBarrier,为什么我要使用CountDownLatch?如果我使用它,是否会获得性能或其他类型的好处?即使我不想重复使用它,我也可以使用CyclicBarrier,对吗?如果是这样,为什么Java提供了两个不同的类,这不是完全可以通过一个类实现吗? - Vj More
1
为什么在只需要一次性屏障时要使用可重用的屏障?很可能,CountDownLatch 的实现针对这种一次性场景进行了优化。正如我所说,我给出的答案并不是非常详细,因此您可能希望 完全阅读 每个 javadoc,以了解为什么要使用其中一个而不是另一个。 - fge

6
“CyclicBarrier可以完成CountDownLatch的所有任务,那么为什么Java标准库中还要有CountDownLatch呢?”
“事实并非如此。”
“两者最大的区别不在于一个可重用而另一个不可重用。区别在于状态改变的时机。对于latch,当某个人(这是关键!)调用countDown()方法时,而对于barrier,则是当一个线程(这是关键!)到达await()方法时。”
“因此,基本上我们在这里有不同的衡量单位-一个以调用countDown()的数量为操作单位,而另一个以等待线程的数量为操作单位。”
“考虑两个例子:”
// thread 1
for (int i = 0; i < 10; i++) {
  latch.countDown() //perfectly fine
}

//thread 2
for (int i = 0; i < 10; i++) {
  barrier.await() //oops 
}

考虑到实际上它们是针对不同事物的计数器,它们的特性在某种程度上会相互交叉。

1
这完美地回答了问题。被接受的答案没有指出两者之间的核心区别。 - KenIchi

2
您谈论的是一个常见问题。有时候JDK提供的一些类在功能上确实有重叠。例如,CountDownLatchSemaphore。基本上您可以用其中一个替换另一个而不会有太大的功能差异。这篇文章:CountDownLatch vs. Semaphore可能会帮助您解决疑惑。
我认为您可以选择一个可以使您的代码更易于理解的选项。在您的情况下,如果您使用CyclicBarrier,它将永远不会重置自己,因此您最好使用CountDownLatch,这样可以使代码更易于理解。
Semaphore的构造函数为例:JDK提供了两个构造函数。
1     public Semaphore(int permits) {
2         sync = new NonfairSync(permits);
3     }

而且。
1     public Semaphore(int permits, boolean fair) {
2         sync = fair ? new FairSync(permits) : new NonfairSync(permits);
3     }

我们可以使用Semaphore(3,false)代替Semaphore(3),两者都是创建非公平版本的信号量。但为什么JDK仍然提供了两个构造函数版本呢?因为从中选择合适的一个可以使代码更易于阅读和理解。


1

以下是我的意见:

CountDownLatch 允许线程 A 等待线程 B,但不要求线程 B 等待线程 A。这可以用于一些场景,其中一个线程需要不被打断地工作,而另一个线程必须在该线程的某些点上进行同步。

另一方面,CyclicBarrier 要求两个线程相互同步,这是两者之间的核心区别。


0
除了标准差异之外,同时拥有它们的原因是它们提供不同的功能,而不是完全重叠。例如:
  1. CyclicBarrier允许您提供一个Runnable,在每次达到屏障点时执行,这可以用于更新一些公共状态,CountDownLatch无法完成此操作。
  2. 在CyclicBarrier中,如果一个线程遇到异常,所有线程都会退出并显示异常,CountDownLatch无法完成此操作。
所以,真正取决于您的用例使用哪个。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接