1

让我们考虑以下代码:

public static void main(String[] args) throws InterruptedException {
    CyclicBarrier cb = new CyclicBarrier(3, () -> {
        logger.info("Barrier action starting");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("Barrier action finishing");
    });
    for (int i = 0; i < 6; i++) {
        int counter = i;
        Thread.sleep(100);
        new Thread(() -> {
            try {
                logger.info("Try to acquire barrier for {}", counter);
                cb.await();
                logger.info("barrier acquired for {}", counter);

            } catch (Exception e) {
                e.printStackTrace();
            }

        }).start();
    }
}

我创建了大小 = 3 的屏障和需要 5 秒的屏障动作。

我看到以下输出:

2019-10-27 15:23:09.393  INFO   --- [       Thread-0] my.playground.RemoteServiceFacade        : Try to acquire barrier for 0
2019-10-27 15:23:09.492  INFO   --- [       Thread-1] my.playground.RemoteServiceFacade        : Try to acquire barrier for 1
2019-10-27 15:23:09.593  INFO   --- [       Thread-2] my.playground.RemoteServiceFacade        : Try to acquire barrier for 2
2019-10-27 15:23:09.594  INFO   --- [       Thread-2] my.playground.RemoteServiceFacade        : Barrier action starting
2019-10-27 15:23:09.693  INFO   --- [       Thread-3] my.playground.RemoteServiceFacade        : Try to acquire barrier for 3
2019-10-27 15:23:09.794  INFO   --- [       Thread-4] my.playground.RemoteServiceFacade        : Try to acquire barrier for 4
2019-10-27 15:23:09.897  INFO   --- [       Thread-5] my.playground.RemoteServiceFacade        : Try to acquire barrier for 5
2019-10-27 15:23:14.594  INFO   --- [       Thread-2] my.playground.RemoteServiceFacade        : Barrier action finishing
2019-10-27 15:23:14.595  INFO   --- [       Thread-2] my.playground.RemoteServiceFacade        : barrier acquired for 2
2019-10-27 15:23:14.595  INFO   --- [       Thread-5] my.playground.RemoteServiceFacade        : Barrier action starting
2019-10-27 15:23:19.596  INFO   --- [       Thread-5] my.playground.RemoteServiceFacade        : Barrier action finishing
2019-10-27 15:23:19.597  INFO   --- [       Thread-0] my.playground.RemoteServiceFacade        : barrier acquired for 0
2019-10-27 15:23:19.597  INFO   --- [       Thread-4] my.playground.RemoteServiceFacade        : barrier acquired for 4
2019-10-27 15:23:19.597  INFO   --- [       Thread-3] my.playground.RemoteServiceFacade        : barrier acquired for 3
2019-10-27 15:23:19.597  INFO   --- [       Thread-1] my.playground.RemoteServiceFacade        : barrier acquired for 1
2019-10-27 15:23:19.597  INFO   --- [       Thread-5] my.playground.RemoteServiceFacade        : barrier acquired for 5

所以我们可以看到:

  1. 第一次障碍行动持续 15:23:09 - 15:23:14
  2. 第二个障碍行动持续 15:23:14 - 15:23:19

但在第一次屏障操作终止后,只有一个线程能够记录:

2019-10-27 15:23:14.595  INFO   --- [       Thread-2] my.playground.RemoteServiceFacade        : barrier acquired for 2

我预计 3 个线程应该能够在大约 15:23:14 获得,因为 CyclicBarrier 大小为 3。

你能解释一下这种行为吗?

附言

我试图运行这段代码很多时间,并且总是得到类似的结果。

PS2。

我试着改变一下时间:

public static void main(String[] args) throws InterruptedException {
    CyclicBarrier cb = new CyclicBarrier(3, () -> {
        logger.info("Barrier action starting");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("Barrier action finishing");
    });
    for (int i = 0; i < 6; i++) {
        int counter = i;
        Thread.sleep(1000);
        new Thread(() -> {
            try {
                logger.info("Try to acquire barrier for {}", counter);
                cb.await();
                logger.info("barrier acquired for {}", counter);

            } catch (Exception e) {
                e.printStackTrace();
            }

        }).start();
    }
}

我看到了预期的结果:

2019-10-27 23:22:14.497  INFO   --- [       Thread-0] my.playground.RemoteServiceFacade        : Try to acquire barrier for 0
2019-10-27 23:22:15.495  INFO   --- [       Thread-1] my.playground.RemoteServiceFacade        : Try to acquire barrier for 1
2019-10-27 23:22:16.495  INFO   --- [       Thread-2] my.playground.RemoteServiceFacade        : Try to acquire barrier for 2
2019-10-27 23:22:16.496  INFO   --- [       Thread-2] my.playground.RemoteServiceFacade        : Barrier action starting
2019-10-27 23:22:16.998  INFO   --- [       Thread-2] my.playground.RemoteServiceFacade        : Barrier action finishing
2019-10-27 23:22:16.998  INFO   --- [       Thread-0] my.playground.RemoteServiceFacade        : barrier acquired for 0
2019-10-27 23:22:16.998  INFO   --- [       Thread-2] my.playground.RemoteServiceFacade        : barrier acquired for 2
2019-10-27 23:22:16.998  INFO   --- [       Thread-1] my.playground.RemoteServiceFacade        : barrier acquired for 1
2019-10-27 23:22:17.495  INFO   --- [       Thread-3] my.playground.RemoteServiceFacade        : Try to acquire barrier for 3
2019-10-27 23:22:18.495  INFO   --- [       Thread-4] my.playground.RemoteServiceFacade        : Try to acquire barrier for 4
2019-10-27 23:22:19.496  INFO   --- [       Thread-5] my.playground.RemoteServiceFacade        : Try to acquire barrier for 5
2019-10-27 23:22:19.499  INFO   --- [       Thread-5] my.playground.RemoteServiceFacade        : Barrier action starting
2019-10-27 23:22:20.002  INFO   --- [       Thread-5] my.playground.RemoteServiceFacade        : Barrier action finishing
2019-10-27 23:22:20.003  INFO   --- [       Thread-5] my.playground.RemoteServiceFacade        : barrier acquired for 5
2019-10-27 23:22:20.003  INFO   --- [       Thread-3] my.playground.RemoteServiceFacade        : barrier acquired for 3
2019-10-27 23:22:20.003  INFO   --- [       Thread-4] my.playground.RemoteServiceFacade        : barrier acquired for 4
4

1 回答 1

1

一个有趣的问题,它不是非常微不足道的,但我会尽量简洁地解释它。

尽管多线程不保证任何类型的执行顺序,但对于这个答案,让我们假设有两个序列首先发生:

  1. 所有线程都在完全相同的时间开始
  2. 所有线程barrier.await()同时调用。

在这种情况下,您会看到类似的输出

Try to acquire barrier for 0
Try to acquire barrier for 1
Try to acquire barrier for 2
Try to acquire barrier for 3
Try to acquire barrier for 4
Try to acquire barrier for 5

您的 6 个线程的当前状态如下:

  1. 线程01 await共享Condition,因为三个线程“尚未”达到障碍

  2. 线程2仍然可以作为屏障的“跳闸”线程运行

  3. Threads 34并将5等待lock.lock屏障的调用(与创建 的Lock实例相同Condition

当屏障看到2它先前记录的线程01到达屏障时,它知道这个循环已经完成并将释放01。但在它释放其他两个线程之前,它需要运行barrierAction你定义为休眠 5 秒的线程,所以它会这样做。

然后你会看到输出

Barrier action starting
Barrier action finishing

线程2仍然持有锁,RUNNABLE并且已经准备好退出屏障,所以它这样做了,你会看到这个输出。

barrier acquired for 2

但在线程2存在之前,它会通知所有其他线程在当前屏障上等待。这是它变得棘手的地方,awaitby 0and1是在 shared 上完成的Condition。它Condition在所有屏障“代”之间共享。因此,即使前两个线程await在第二个 3 线程之前locking,当 asignalAll完成时,第一代线程仍然必须等待轮到它们唤醒。

此时我们有 5 个线程处于BLOCKED(3, 4 & 5) 或TIMED_WAITING(0, 1) 状态。在这个例子中,当他们阻塞/等待Lock. 如果它们都按顺序发生,关键部分的队列将是:

Thread-0 -> Thread-1 -> Thread-5 -> Thread-4 -> Thread-3 
   |                                               |
  TAIL                                            HEAD

这样下一个线程将被释放Thread-3,然后4和随后5。队列看起来像这样的原因是因为所有线程lock同时到达并且它们都排队,线程0并且1显然首先到达它,因此使其进入屏障的关键部分,但是await当线程2进入唤醒它们时,但现在0并排1在队列的末尾,接下来将触发 3、4 和 5。

当线程2离开屏障并且线程signalAll将运行并且由于它们是第二代的一部分将暂停,直到线程通过并触发操作。然后打印出来345barrier

Barrier action starting
Barrier action finishing

最后,线程5signalAll再次唤醒,其余线程将完成唤醒。

在这种情况下,您将看到线程5首先完成,其余的跟随

barrier acquired for 5
barrier acquired for 0
barrier acquired for 1
barrier acquired for 3
barrier acquired for 4
于 2019-10-27T13:24:38.780 回答