CountDownLatch VS CyclicBarrier

案例分析​: 100 米短跑比赛, 运动员从赛前热身准备到比赛结束裁判宣布运动员排名

public class AnswerApp {
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 比赛选手个数
private final static int COUNT = 8;
// 比赛最终排名
private static Queue<Integer> rank = new LinkedList<>();
private static CountDownLatch countDownLatch = new CountDownLatch(COUNT);
private static Random random = new Random();

public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(COUNT);
ExecutorService executorService = Executors.newFixedThreadPool(COUNT);
for (int i = 1; i <= COUNT; i++) {
final int n = i;
executorService.execute(() -> {
try {
// 模拟选手由于个人原因需要花费部分时间进行赛前热身
Thread.sleep(random.nextInt(5) * 1000);

// 等待所有任务准备就绪
System.out.println(MessageFormat.format("{0} 赛道[{1}]选手已准备就绪...", LocalDateTime.now().format(DATETIME_FORMATTER), n));

// 选手在起跑线前准备
cyclicBarrier.await();

// 只打印一条记录即可
if (n == 1) {
System.out.println();
System.out.println(MessageFormat.format("{0} 准备开始啦~~~倒数 3 2 1...", LocalDateTime.now().format(DATETIME_FORMATTER)));
System.out.println();
}

// 模拟所有选手都已经准备完毕, 倒数 3 2 1
Thread.sleep(3000);

System.out.println(MessageFormat.format("{0} 我是赛道[{1}]选手, 我开始跑啦", LocalDateTime.now().format(DATETIME_FORMATTER), n));
int time = random.nextInt(10) + 5;

// 只打印一条记录即可
if (n == 1) {
System.out.println(MessageFormat.format("\n{0} 选手们都在疯狂奔跑中...\n", LocalDateTime.now().format(DATETIME_FORMATTER)));
}

// 模拟选手的跑完全程的耗时
Thread.sleep(time * 1000);

System.out.println(MessageFormat.format("{0} 赛道[{1}]选手达到终点, 用时[{2}]s.", LocalDateTime.now().format(DATETIME_FORMATTER), n, time));

rank.add(n);
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
System.out.println(MessageFormat.format("{0} 选手[{1}]退出了比赛, 退赛原因[{2}]", LocalDateTime.now().format(DATETIME_FORMATTER), n, e.getMessage()));
} finally {
countDownLatch.countDown(); // 到达终点计数器减1
}
});
}

try {
// 阻塞, 直到所有线程都执行完毕, 即所有选手都跑完比赛(包括退赛)
countDownLatch.await();

System.out.println();
System.out.println(MessageFormat.format("{0} 最终排名[{1}]", LocalDateTime.now().format(DATETIME_FORMATTER), rank));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 关闭线程池
executorService.shutdown();
}
}
}
  • CountDownLatch​: 用于记录选手达到终点的计数器, 计数器为0时, 所有选手都结束比赛(跑完比赛和退赛)
  • CyclicBarrier​​: 用于所有选手在起跑线相互等待, 等待所有选手都准备就绪, 再进行比赛[​​有重置功能, 调用reset()方法​​]

​程序运行结果​

2019-07-06 11:46:18 赛道[2]选手已准备就绪...
2019-07-06 11:46:18 赛道[3]选手已准备就绪...
2019-07-06 11:46:19 赛道[4]选手已准备就绪...
2019-07-06 11:46:19 赛道[6]选手已准备就绪...
2019-07-06 11:46:20 赛道[7]选手已准备就绪...
2019-07-06 11:46:21 赛道[1]选手已准备就绪...
2019-07-06 11:46:21 赛道[5]选手已准备就绪...
2019-07-06 11:46:21 赛道[8]选手已准备就绪...

2019-07-06 11:46:21 准备开始啦~~~倒数 3 2 1...

2019-07-06 11:46:24 我是赛道[8]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[2]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[6]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[3]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[4]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[7]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[5]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[1]选手, 我开始跑啦

2019-07-06 11:46:24 选手们都在疯狂奔跑中...

2019-07-06 11:46:31 赛道[1]选手达到终点, 用时[7]s.
2019-07-06 11:46:32 赛道[4]选手达到终点, 用时[8]s.
2019-07-06 11:46:32 赛道[5]选手达到终点, 用时[8]s.
2019-07-06 11:46:34 赛道[3]选手达到终点, 用时[10]s.
2019-07-06 11:46:37 赛道[2]选手达到终点, 用时[13]s.
2019-07-06 11:46:37 赛道[7]选手达到终点, 用时[13]s.
2019-07-06 11:46:38 赛道[8]选手达到终点, 用时[14]s.
2019-07-06 11:46:38 赛道[6]选手达到终点, 用时[14]s.

2019-07-06 11:46:38 最终排名[[1, 4, 5, 3, 2, 7, 8, 6]]

 

Semaphore

案例分析​: 一个工厂有 n-MACHINE 台机器, 但是有 n-WORKER 个工人, 一台机器同时只能被一个工人使用, 只有使用完了,其他工人才能继续使用

public class AnswerApp {
/** 工人数量 */
private static final int WORKER = 10;
/** 机器数量 */
private static final int MACHINE = 3;

private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

public static void main(String[] args) {
final Semaphore semaphore = new Semaphore(MACHINE);
ExecutorService executorService = Executors.newCachedThreadPool();

for (int i = 1; i <= WORKER; i++) {
final int index = i;
executorService.execute(() -> {
try {
// 工人获取一台机器用于作业的许可
semaphore.acquire();

System.out.println(MessageFormat.format("{0} 工作人员[{1}] take up a machine.",
LocalDateTime.now().format(DATETIME_FORMATTER), index));

Thread.sleep(5000);

System.out.println(MessageFormat.format("{0} 工作人员[{1}] release a machine.",
LocalDateTime.now().format(DATETIME_FORMATTER), index));

// 工厂生产完毕, 释放机器
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}

executorService.shutdown();
}
}

​程序运行结果​

2019-07-06 13:42:40 工作人员[2] take up a machine.
2019-07-06 13:42:40 工作人员[1] take up a machine.
2019-07-06 13:42:40 工作人员[3] take up a machine.
2019-07-06 13:42:45 工作人员[3] release a machine.
2019-07-06 13:42:45 工作人员[2] release a machine.
2019-07-06 13:42:45 工作人员[1] release a machine.
2019-07-06 13:42:45 工作人员[4] take up a machine.
2019-07-06 13:42:45 工作人员[7] take up a machine.
2019-07-06 13:42:45 工作人员[5] take up a machine.
2019-07-06 13:42:50 工作人员[4] release a machine.
2019-07-06 13:42:50 工作人员[7] release a machine.
2019-07-06 13:42:50 工作人员[6] take up a machine.
2019-07-06 13:42:50 工作人员[8] take up a machine.
2019-07-06 13:42:50 工作人员[5] release a machine.
2019-07-06 13:42:50 工作人员[9] take up a machine.
2019-07-06 13:42:55 工作人员[8] release a machine.
2019-07-06 13:42:55 工作人员[9] release a machine.
2019-07-06 13:42:55 工作人员[6] release a machine.
2019-07-06 13:42:55 工作人员[10] take up a machine.
2019-07-06 13:43:00 工作人员[10] release a machine.

 

总结

  • CountDownLatch​: 一般用于某个线程A等待若干个其他线程执行完任务之后,它再执行
  • 裁判等待所有的选手都跑完再进行公布排名
  • CyclicBarrier​: 一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行
  • 起跑准备时, 所有选手都相互等待, 等待所有人都准备好再一起开跑
  • CountDownLatch是​​不能够重用​​的,而CyclicBarrier是​​可以重用​​的(通过调用 reset 方法)
  • Semaphore​: 其实和锁有点类似,它一般用于控制对某组资源的访问权限