原文作者:洲洋1984

原文地址:Java 并发包中的高级同步工具

Java 中的并发包指的是 java.util.concurrent(简称 JUC)包和其子包下的类和接口,它为 Java 的并发提供了各种功能支持,比如:

  • 提供了线程池的创建类 ThreadPoolExecutor、Executors 等;
  • 提供了各种锁,如 Lock、ReentrantLock 等;
  • 提供了各种线程安全的数据结构,如 ConcurrentHashMap、LinkedBlockingQueue、DelayQueue 等;
  • 提供了更加高级的线程同步结构,如 CountDownLatch、CyclicBarrier、Semaphore 、Phaser等。

在前面的章节中我们已经详细地介绍了线程池的使用、线程安全的数据结构等,本文我们就重点学习一下 Java 并发包中更高级的线程同步类:CountDownLatch、CyclicBarrier、Semaphore 和 Phaser 等。

一、CountDownLatch 介绍和使用

CountDownLatch(闭锁)可以看作一个只能做减法的计数器,可以让一个或多个线程等待排队执行。CountDownLatch 有两个重要的方法:

  • countDown():使计数器减 1;
  • await():当计数器不为 0 时,则调用该方法的线程阻塞,当计数器为 0 时,可以唤醒等待的一个或者全部线程。

CountDownLatch 使用场景:以生活中的情景为例,比如去医院体检,通常人们会提前去医院排队,但只有等到医生开始上班,才能正式开始体检,医生也要给所有人体检完才能下班,这种情况就要使用 CountDownLatch,流程为:患者排队 → 医生上班 → 体检完成 → 医生下班。

CountDownLatch 示例代码如下:

// 医院闭锁
CountDownLatch hospitalLatch = new CountDownLatch(1);
// 患者闭锁
CountDownLatch patientLatch = new CountDownLatch(5);
System.out.println("患者排队");
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {final int j = i;executorService.execute(() -> {try {hospitalLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("体检:" + j);patientLatch.countDown();});
}
System.out.println("医生上班");
hospitalLatch.countDown();
patientLatch.await();
System.out.println("医生下班");
executorService.shutdown();

以上程序执行结果如下:

患者排队

医生上班

体检:4

体检:0

体检:1

体检:3

体检:2

医生下班

二、CyclicBarrier 介绍和使用

CyclicBarrier(循环屏障),通过它可以实现让一组线程等待满足某个条件后同时执行。CyclicBarrier 经典使用场景是公交发车,为了简化理解我们这里定义,每辆公交车只要上满 4 个人就发车,后面来的人都会排队依次遵循相应的标准。

它的构造方法为 CyclicBarrier(int parties,Runnable barrierAction) 其中,parties 表示有几个线程来参与等待,barrierAction 表示满足条件之后触发的方法。CyclicBarrier 使用 await() 方法来标识当前线程已到达屏障点,然后被阻塞。

CyclicBarrier 示例代码如下:

import java.util.concurrent.*;
public class CyclicBarrierTest {public static void main(String[] args) throws InterruptedException {CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {@Overridepublic void run() {System.out.println("发车了");}});for (int i = 0; i < 4; i++) {new Thread(new CyclicWorker(cyclicBarrier)).start();}}static class CyclicWorker implements Runnable {private CyclicBarrier cyclicBarrier;CyclicWorker(CyclicBarrier cyclicBarrier) {this.cyclicBarrier = cyclicBarrier;}@Overridepublic void run() {for (int i = 0; i < 2; i++) {System.out.println("乘客:" + i);try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}}}
}

以上程序执行结果如下:

乘客:0

乘客:0

乘客:0

乘客:0

发车了

乘客:1

乘客:1

乘客:1

乘客:1

发车了

三、Semaphore 介绍和使用

Semaphore(信号量)用于管理多线程中控制资源的访问与使用。Semaphore 就好比停车场的门卫,可以控制车位的使用资源。比如来了 5 辆车,只有 2 个车位,门卫可以先放两辆车进去,等有车出来之后,再让后面的车进入。

Semaphore 示例代码如下:

Semaphore semaphore = new Semaphore(2);
ThreadPoolExecutor semaphoreThread = new ThreadPoolExecutor(10, 50, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
for (int i = 0; i < 5; i++) {semaphoreThread.execute(() -> {try {// 堵塞获取许可semaphore.acquire();System.out.println("Thread:" + Thread.currentThread().getName() + " 时间:" + LocalDateTime.now());TimeUnit.SECONDS.sleep(2);// 释放许可semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}});
}

以上程序执行结果如下:

Thread:pool-1-thread-1 时间:2019-07-10 21:18:42

Thread:pool-1-thread-2 时间:2019-07-10 21:18:42

Thread:pool-1-thread-3 时间:2019-07-10 21:18:44

Thread:pool-1-thread-4 时间:2019-07-10 21:18:44

Thread:pool-1-thread-5 时间:2019-07-10 21:18:46

四、Phaser(移相器)

是 JDK 7 提供的,它的功能是等待所有线程到达之后,才继续或者开始进行新的一组任务。比如有一个旅行团,我们规定所有成员必须都到达指定地点之后,才能发车去往景点一,到达景点之后可以各自游玩,之后必须全部到达指定地点之后,才能继续发车去往下一个景点,类似这种场景就非常适合使用 Phaser。

Phaser 示例代码如下:

public class Lesson5\_6 {public static void main(String[] args) throws InterruptedException {Phaser phaser = new MyPhaser();PhaserWorker[] phaserWorkers = new PhaserWorker[5];for (int i = 0; i < phaserWorkers.length; i++) {phaserWorkers[i] = new PhaserWorker(phaser);// 注册 Phaser 等待的线程数,执行一次等待线程数 +1phaser.register();}for (int i = 0; i < phaserWorkers.length; i++) {// 执行任务new Thread(new PhaserWorker(phaser)).start();}}static class PhaserWorker implements Runnable {private final Phaser phaser;public PhaserWorker(Phaser phaser) {this.phaser = phaser;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " | 到达" );phaser.arriveAndAwaitAdvance(); // 集合完毕发车try {Thread.sleep(new Random().nextInt(5) * 1000);System.out.println(Thread.currentThread().getName() + " | 到达" );phaser.arriveAndAwaitAdvance(); // 景点 1 集合完毕发车Thread.sleep(new Random().nextInt(5) * 1000);System.out.println(Thread.currentThread().getName() + " | 到达" );phaser.arriveAndAwaitAdvance(); // 景点 2 集合完毕发车} catch (InterruptedException e) {e.printStackTrace();}}}// Phaser 每个阶段完成之后的事件通知static class MyPhaser extends Phaser{@Overrideprotected boolean onAdvance(int phase, int registeredParties) { // 每个阶段执行完之后的回调switch (phase) {case 0:System.out.println("==== 集合完毕发车 ====");return false;case 1:System.out.println("==== 景点1集合完毕,发车去下一个景点 ====");return false;case 2:System.out.println("==== 景点2集合完毕,发车回家 ====");return false;default:return true;}}}
}

以上程序执行结果如下:

Thread-0 | 到达

Thread-4 | 到达

Thread-3 | 到达

Thread-1 | 到达

Thread-2 | 到达

==== 集合完毕发车 ====

Thread-0 | 到达

Thread-4 | 到达

Thread-1 | 到达

Thread-3 | 到达

Thread-2 | 到达

==== 景点1集合完毕,发车去下一个景点 ====

Thread-4 | 到达

Thread-3 | 到达

Thread-2 | 到达

Thread-1 | 到达

Thread-0 | 到达

==== 景点2集合完毕,发车回家 ====

相关面试题

1.以下哪个类用于控制某组资源的访问权限?

  • A:Phaser
  • B:Semaphore
  • C:CountDownLatch
  • D:CyclicBarrier

答:B

2.以下哪个类不能被重用?

  • A:Phaser
  • B:Semaphore
  • C:CountDownLatch
  • D:CyclicBarrier

答:C

3.以下哪个方法不属于 CountDownLatch 类?

  • A:await()
  • B:countDown()
  • C:getCount()
  • D:release()

答:D。release() 是 Semaphore 的释放许可的方法,CountDownLatch 类并不包含此方法。

4.CyclicBarrier 与 CountDownLatch 有什么区别?

答:CyclicBarrier 与 CountDownLatch 本质上都是依赖 volatile 和 CAS 实现的,它们区别如下:

  • CountDownLatch 只能使用一次,而 CyclicBarrier 可以使用多次。
  • CountDownLatch 是手动指定等待一个或多个线程执行完成再执行,而 CyclicBarrier 是 n 个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。

5.以下哪个类不包含 await() 方法?

  • A:Semaphore
  • B:CountDownLatch
  • C:CyclicBarrier

答:A

6.以下程序执行花费了多长时间?

Semaphore semaphore = new Semaphore(2);
ThreadPoolExecutor semaphoreThread = new ThreadPoolExecutor(10, 50, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
for (int i = 0; i < 3; i++) {semaphoreThread.execute(() -> {try {semaphore.release();System.out.println("Hello");TimeUnit.SECONDS.sleep(2);semaphore.acquire();} catch (InterruptedException e) {e.printStackTrace();}});
}
  • A:1s 以内
  • B:2s 以上

答:A。循环先执行了 release() 也就是释放许可的方法,因此程序可以一次性执行 3 个线程,同时会在 1s 以内执行完。

7.Semaphore 有哪些常用的方法?

答:常用方法如下:

  • acquire():获取一个许可。
  • release():释放一个许可。
  • availablePermits():当前可用的许可数。
  • acquire(int n):获取并使用 n 个许可。
  • release(int n):释放 n 个许可。

8.Phaser 常用方法有哪些?

答:常用方法如下:

  • register():注册新的参与者到 Phaser
  • arriveAndAwaitAdvance():等待其他线程执行
  • arriveAndDeregister():注销此线程
  • forceTermination():强制 Phaser 进入终止态
  • isTerminated():判断 Phaser 是否终止

9.以下程序是否可以正常执行?“发车了”打印了多少次?

import java.util.concurrent.*;
public class TestMain {public static void main(String[] args) {CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {@Overridepublic void run() {System.out.println("发车了");}});for (int i = 0; i < 4; i++) {new Thread(new CyclicWorker(cyclicBarrier)).start();}}static class CyclicWorker implements Runnable {private CyclicBarrier cyclicBarrier;CyclicWorker(CyclicBarrier cyclicBarrier) {this.cyclicBarrier = cyclicBarrier;}@Overridepublic void run() {for (int i = 0; i < 2; i++) {System.out.println("乘客:" + i);try {cyclicBarrier.await();System.out.println("乘客 II:" + i);cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}}}
}

答:可以正常执行,因为执行了两次 await(),所以“发车了”打印了 4 次。

总结

本文我们介绍了四种比 synchronized 更高级的线程同步类,其中 CountDownLatch、CyclicBarrier、Phaser 功能比较类似都是实现线程间的等待,只是它们的侧重点有所不同,其中 CountDownLatch 一般用于等待一个或多个线程执行完,才执行当前线程,并且 CountDownLatch 不能重复使用;CyclicBarrier 用于等待一组线程资源都进入屏障点再共同执行;Phaser 是 JDK 7 提供的功能更加强大和更加灵活的线程辅助工具,等待所有线程达到之后,继续或开始新的一组任务,Phaser 提供了动态增加和消除线程同步个数功能。而 Semaphore 提供的功能更像锁,用于控制一组资源的访问权限。

Java并发编程—线程同步类相关推荐

  1. Java并发编程:Thread类的使用

    为什么80%的码农都做不了架构师?>>>    Java并发编程:Thread类的使用 在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学 ...

  2. Java 并发编程 -- 线程池源码实战

    一.概述 小编在网上看了好多的关于线程池原理.源码分析相关的文章,但是说实话,没有一篇让我觉得读完之后豁然开朗,完完全全的明白线程池,要么写的太简单,只写了一点皮毛,要么就是是晦涩难懂,看完之后几乎都 ...

  3. python 线程同步_Python并发编程-线程同步(线程安全)

    Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...

  4. Java并发编程—线程间协作方式wait()、notify()、notifyAll()和Condition

    原文作者:Matrix海 子 原文地址:Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 目录 一.wait().notify()和notifyA ...

  5. Java并发编程:同步容器

    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...

  6. Java并发编程-线程安全基础

    线程安全基础 1.线程安全问题 2.账户取款案例 3.同步代码块synchronized synchronized的理解 java中有三大变量的线程安全问题 在实例方法上使用synchronized ...

  7. 【Java并发编程 】同步——volatile 关键字

    英 /ˈvɒlətaɪl/ 我了太噢(记不住单词怎么读) 一.volatile的介绍? volatile是一个轻量级的synchronized,一般作用与变量,在多处理器开发的过程中保证了内存的可见性 ...

  8. Java多线程编程——线程同步与线程安全问题及synchronized关键字

    在多线程环境下,我们常常需要让多个线程同时去操作同一资源.在某些情况下,这种情形会导致程序的运行结果出现差错.专业上的,当多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不 ...

  9. Java并发编程——线程池的使用

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

最新文章

  1. 通俗易懂地讲解 Java 的注解
  2. PICRUSt:16S预测宏基因组-扩增子分析锦上添花
  3. mysql datetime转换为date_MySQL date,datetime,timestamp区别及相互转换
  4. arduino出现java错误_arduino在上传的时候出现这样的错误,在线求助大神!!!
  5. 无线路由器说说2.4G和5G Wi-Fi的区别
  6. 正态分布的峰度和偏度分别为_ML中的正态分布
  7. django05:ORM示例--person 增删改查
  8. (九十)使用多个storyboard+代码实现控制器的分开管理
  9. Linux虚拟化KVM-Qemu分析(二)之ARMv8虚拟化
  10. vs在linux下的环境搭建,linux下vscode环境配置
  11. 剑指offer面试题04. 二维数组中的查找(Array)
  12. 64位Win8企业版出现“Unknown Hard Error”系统警告的一个解决方法
  13. js使用的一些实用技巧
  14. python商品总数抹零_销售发货单
  15. codeblocks 16.01 汉化包下载地址及方法
  16. .net Response导出excel表格边框设置日期时间设置
  17. 把咖啡这桩生意放进独立站,总共分几步?(上)
  18. 习题--答案--22/6/8
  19. MTK机器原始OTA更新方法
  20. 游戏笔记本电脑推荐 多功能游戏本你见过吗?

热门文章

  1. asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证
  2. C# 汉字转拼音(全拼)
  3. PXE 01-PXE介绍
  4. 洛谷P2751 [USACO4.2]工序安排Job Processing
  5. SQLServer常见性能问题
  6. Windows Installer (MSI) 详解 参数介绍
  7. Windows下安装APM大全
  8. Linux学习笔记:安装python
  9. spring cloud微服务分布式云架构(四)-断路器(Hystrix)
  10. 设置路由器端口转发功能如何操作