当通过的线程数量小于屏障限制时,屏障(例如 CyclicBarrier)是不是会导致死锁?

Posted

技术标签:

【中文标题】当通过的线程数量小于屏障限制时,屏障(例如 CyclicBarrier)是不是会导致死锁?【英文标题】:Does barrier (e.g. CyclicBarrier) cause deadlock when the amount of thread passed it is smaller than the barrier limit?当通过的线程数量小于屏障限制时,屏障(例如 CyclicBarrier)是否会导致死锁? 【发布时间】:2018-12-20 21:47:45 【问题描述】:

运行以下代码时,2个启动线程将被CyclicBarrier *对象锁定,无限等待第三个线程解锁

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class MainDeadlock 
  public static void main(String[] args) throws InterruptedException 
    final CyclicBarrier c = new CyclicBarrier(3); 
    Runnable r = () -> 
            try 
                c.await();
             catch (InterruptedException | BrokenBarrierException e) 
                e.printStackTrace();
            
            System.out.println("Run!");
    ;
    new Thread(r).start();
    new Thread(r).start();

所以 2 个启动的 线程 正在等待第三个线程来解决这个 障碍。但是,根据CyclicBarrier的Java API文档,CyclicBarrier

一种同步辅助工具,允许一组线程相互等待以达到共同的障碍点

我对他们如何“互相等待”感到困惑

问题:“互相等待”是否意味着循环等待?如果是这样,怎么做?严格来说,这是一种僵局吗?

【问题讨论】:

我真的建议你去进一步阅读有关死锁的知识。这不是一个。同步辅助的使用不当会导致所有线程无限期地等待吗?是的。这和死锁一样吗?没有。 这个比较复杂,不知道CyclicBarrier的实现方式 【参考方案1】:

您可以将CyclicBarrier 视为根本不了解线程。可以这样想:

    屏障保持对await() 的调用记录。 当await() 被调用时,代码阻塞(方法不返回),但屏障增加了它的计数。 当计数达到构造时给定的parties 值时,计数被重置,并且在调用await() 时被阻塞的所有线程都被释放(即方法返回)。

因此,在您的情况下,对 await() 的调用在第三次调用发生之前不会返回,因此您的 2 个现有线程实际上被卡住了。这在技术上并不是一个死锁,因为它可以很容易地摆脱(通过再次调用await())。

之所以称为循环是因为一旦计数被重置并且线程被释放,它就可以再次使用。一个典型的用法是将parties 设置为将在其上同步的线程数,并且这些线程都进入某种循环,由此屏障用于确保没有线程移动到下一次迭代,直到所有其他线程也已完成当前迭代。

【讨论】:

【参考方案2】:

关于将情况视为死锁所需的循环等待条件,Wikipedia says:

每个进程都必须等待一个资源 另一个进程,它又在等待第一个进程 释放资源。一般来说,有一组等待进程, P = P1, P2, ..., PN,这样 P1 正在等待由 P2,P2 正在等待 P3 持有的资源,依此类推,直到 PN 等待 P1 持有的资源。

您有一组进程 P1 和 P2。他们正在等待某事,但他们没有等待 P3,因为不存在这样的进程。因此,这不是死锁。

也不满足以下条件:

等待或资源持有一个进程当前持有 至少一项资源 并请求额外的资源 由其他进程持有。

(强调我的)。您没有任何进程持有任何资源,因为不存在第三个进程。

【讨论】:

【参考方案3】:

从技术上讲,这不是死锁,因为已经在屏障处的两个线程没有相互阻塞,它们正在等待第三个永远不会到达的线程。

但最终结果与死锁非常相似,一开始可能会令人困惑。

措辞也有点令人困惑,因为从技术上讲,在具有限制 n 的循环障碍中,第一个 n-1 线程正在等待 nth 线程。

但这与死锁之间的主要区别在于您如何解决它们:线程太少的循环障碍将通过更多线程到达来解决,在死锁中,唯一的“解决方案”是已经杀死其中一个线程等待。

【讨论】:

最终结果相似并不真正相关(while (true); 具有相似的最终结果......),“死锁”有一个非常具体的定义,这并不满足它。 @Michael 没错。它只是相关的,因为它是问题中混淆的根源之一。

以上是关于当通过的线程数量小于屏障限制时,屏障(例如 CyclicBarrier)是不是会导致死锁?的主要内容,如果未能解决你的问题,请参考以下文章

Java CyclicBarrier

CyclicBarrier总结

CyclicBarrier循环屏障相关

java并发之同步辅助类

CyclicBarrier用例源码分析讲解

什么是内存模型的happens-before