CountDownLatch和CyclicBarrier有什么区别?

CountDownLatch有时被称为“闭锁”,其作用相当于一扇门:在CountDownLatch达到结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。当CountDownLatch到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。CountDownLatch可以用来确保某些活动直到其它活动都完成后才继续执行。

CyclicBarrier有时被称为“栅栏”,其作用与CountDownLatch类似,它能阻塞一组线程直到某个事件发生。CyclicBarrier与CountDownLatch的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。CountDownLatch用于等待事件,而CyclicBarrier用于等待其它线程。

总结起来两者区别如下:

  • CountDownLatch是不可重置的,所以无法重用;而CyclicBarrier则没有这种限制,可以重用。
  • CountDownLatch的基本操作组合是countDown/await。调用await的线程阻塞等待countDown足够的次数,不管你是在一个线程还是多个线程里countDown,只要次数足够即可。所以说CountDownLatch操作的是事件。
  • CyclicBarrier的基本操作组合,则就是await。当所有的伙伴(parties)都调用了await,才会继续进行任务,并自动进行重置。注意,正常情况下,CyclicBarrier的重置都是自动发生的,如果我们调用reset方法,但还有线程在等待,就会导致等待线程被打扰,抛出BrokenBarrierException异常。CyclicBarrier侧重点是线程,而不是调用事件,它的典型应用场景是用来等待并发线程结束。

扩展知识

1、用CountDownLatch实现排队场景

还记得上一讲中的排队问题么?如果不记得,可以点击[~lxm:这里]。如果用CountDownLatch去实现这个排队场景,该怎么做呢?假设有10个人排队,我们将其分成5个人一批,通过CountDownLatch来协调批次。

import java.util.concurrent.CountDownLatch;
public class LatchSample {
  public static void main(String[] args) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(6);
    for (int i = 0; i < 5; i++) {
      Thread t = new Thread(new FirstBatchWorker(latch));
      t.start();
    }
    for (int i= 0; i < 5; i++) {
      Thread t = new Thread(new SecondBatchWorker(latch));
      t.start();
    }
    // 注意:这里是演示目的的逻辑,并不是推荐的协调方式
    while (latch.getCount() != 1) {
      Thread.sleep(100L);
    }
    System.out.println("Wait for first batch finish");
    latch.countDown();
  }

  static class FirstBatchWorker implements Runnable {
    private CountDownLatch latch;
    public FirstBatchWorker(CountDownLatch latch) {
      this.latch = latch;
    }
    @Override
    public void run() {
      System.out.println("First batch executed!");
      latch.countDown();
    }
  }

  static class SecondBatchWorker implements Runnable {
    private CountDownLatch latch;
    public SecondBatchWorker(CountDownLatch latch) {
      this.latch = latch;
    }
    @Override
    public void run() {
      try {
        latch.await();
        System.out.println("Second batch executed!");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

CountDownLatch的调度方式相对简单,后一批次的线程进行await,等待前一批countDown足够多次。这个例子也从侧面体现出了它的局限性,虽然它也能够支持10个人排队的情况,但是因为不能重用,如果要支持更多人排队,就不能依赖一个CountDownLatch进行了,其编译运行输出如下:

First batch executed!
First batch executed!
First batch executed!
First batch executed!
First batch executed!
Wait for first batch finish
Second batch executed!
Second batch executed!
Second batch executed!
Second batch executed!
Second batch executed!

在实际应用中的条件依赖,往往没有这么别扭,CountDownLatch用于线程间等待操作结束是非常简单普遍的用法。通过countDown/await组合进行通信是很高效的,通常不建议使用例子里那个循环等待方式。

2、用CyclicBarrier实现排队场景

如果用CyclicBarrier来表达这个场景呢?我们知道CyclicBarrier其实反映的是线程并行运行时的协调,在下面的示例里,从逻辑上,5个工作线程其实更像是代表了5个可以就绪的空车,而不再是5个乘客,对比前面CountDownLatch的例子更有助于我们区别它们的抽象模型。请看下面的示例代码:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierSample {
  public static void main(String[] args) throws InterruptedException {
    CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
      @Override
      public void run() {
        System.out.println("Action...GO again!");
      }
    });
    for (int i = 0; i < 5; i++) {
      Thread t = new Thread(new CyclicWorker(barrier));
      t.start();
    }
  }

  static class CyclicWorker implements Runnable {
    private CyclicBarrier barrier;
    public CyclicWorker(CyclicBarrier barrier) {
      this.barrier = barrier;
    }
    @Override
    public void run() {
      try {
        for (int i = 0; i < 3; i++) {
          System.out.println("Executed!");
          barrier.await();
        }
      } catch (BrokenBarrierException | InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

因为CyclicBarrier会自动进行重置,所以这个逻辑其实可以非常自然的支持更多排队人数。其编译运行输出如下:

Executed!
Executed!
Executed!
Executed!
Executed!
Action...GO again!
Executed!
Executed!
Executed!
Executed!
Executed!
Action...GO again!
Executed!
Executed!
Executed!
Executed!
Executed!
Action...GO again!

Java 7开始并发类库还提供了Phaser,功能与CountDownLatch很接近,但是它允许线程动态地注册到Phaser上面,而CountDownLatch显然是不能动态设置的。Phaser的设计初衷是,实现多个线程类似步骤、阶段场景的协调,线程注册等待屏障条件触发,进而协调彼此间行动。

猜你喜欢

转载自blog.csdn.net/qweqwruio/article/details/81359780