Java 并发包中的高级同步工具

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

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

在前面的章节中我们已经详细地介绍了线程池的使用、线程安全的数据结构等,本文我们就重点学习一下 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() {
@Override
public 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;
}
@Override
public 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 等待的线程数,执行一次等待线程数 +1
phaser.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;
}
@Override
public 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{
@Override
protected 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() {
@Override
public 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;
}
@Override
public 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” ,将会有大礼相送!!! 祝各位面试成功!!!



%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.png)

Java 并发包中的高级同步工具的更多相关文章

  1. Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理

    Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...

  2. Java 并发包中的读写锁及其实现分析

    1. 前言 在Java并发包中常用的锁(如:ReentrantLock),基本上都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时 刻可以允许多个读线程访问,但是在写线程访问时,所有 ...

  3. Java并发包中Semaphore的工作原理、源码分析及使用示例

    1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...

  4. Java并发包中CountDownLatch的工作原理、使用示例

    1. CountDownLatch的介绍 CountDownLatch是一个同步工具,它主要用线程执行之间的协作.CountDownLatch 的作用和 Thread.join() 方法类似,让一些线 ...

  5. Java多线程与并发库高级应用-工具类介绍

    java.util.concurrent.Lock 1.Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互 ...

  6. Java并发包中Lock的实现原理

    1. Lock 的简介及使用 Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制.本质上Lock仅仅是一个接口(位于源码包中的java\util\concurrent\l ...

  7. Java并发包中常用类小结(一)

    从JDK1.5以后,Java为我们引入了一个并发包,用于解决实际开发中经常用到的并发问题,那我们今天就来简单看一下相关的一些常见类的使用情况. 1.ConcurrentHashMap Concurre ...

  8. Java并发之CyclicBarrier 可重用同步工具类

    package com.thread.test.thread; import java.util.Random; import java.util.concurrent.*; /** * Cyclic ...

  9. Java并发之CountDownLatch 多功能同步工具类

    package com.thread.test.thread; import java.util.Random; import java.util.concurrent.*; /** * CountD ...

随机推荐

  1. saltstack的配置配置

    一.为不同的环境设置不同的文件目录 1.1 修改配置文件 /etc/salt/master [root@node1 salt]# vim /etc/salt/master file_roots: ba ...

  2. 洛谷$P2570\ [ZJOI2010]$贪吃的老鼠 网络流+二分

    正解:网络流+二分 解题报告: 传送门$QwQ$ 和上一题有点儿像,,,?$QwQ$但是比上一题要有趣很多$QwQ$ 首先把大致思路捋下?依然是.二分出每个奶酪的开始和结束时间,然后check下最大流 ...

  3. 关于Mac VMFusion Centos7虚拟机网络的配置

    1.环境配置: 创建完快照后启动虚拟机,使用root用户和root密码登录系统 1.1 停止防火墙 #停止防火墙 [root@localhost ~]#systemctl stop firewalld ...

  4. spring cloud微服务快速教程之(三)声明式访问Feign、负载均衡Ribbon

    0-前言 eureka实际上已经集成了负载均衡调度框架Ribbon: 我们有了各个微服务了,那怎么来调用他们呢,一种方法是可以使用 RestTemplate(如:String str= restTem ...

  5. MacBook Pro 入手一年了,到底香不香?

    最近又有小伙伴问到底值不值得入手一台 MacBook Pro,松哥自己在 2018 年 10 月份的时候入手了一台,到现在为止,也用了一年多了,今天就来和小伙伴们聊一聊使用感受,至于到底值不值,需要大 ...

  6. fastjson使用详解

    目录 二.fastjson使用 三.fastjson 常用 API 四.fastjson使用演示 测试类准备 1.java类转换为json字符串 2.json字符串转为java类 五.fastjson ...

  7. Spring Cloud(一):服务注册中心Eureka

    Spring Cloud 基于 Netflix 的几个开源项目进行了封装,提供包括服务注册与发现(Eureka),智能路由(Zuul),熔断器(Hystrix),客户端负载均衡(Ribbon)等在内的 ...

  8. 三、Spring Cloud之软负载均衡 Ribbon

    前言 上一节我们已经学习了Eureka 注册中心,其实我们也使用到了Ribbon ,只是当时我们没有细讲,所以我们现在一起来学习一下Ribbon. 什么是Ribbon 之前接触到的负载均衡都是硬负载均 ...

  9. 借助 dp 公式去优化

    题目描述 一天,神犇和 LCR 在玩扑克牌.他们玩的是一种叫做“接竹竿”的游戏. 游戏规则是:一共有 nnn 张牌,每张牌上有一个花色 ccc 和一个点数 vvv,花色不超过 kkk 种.将这些牌依次 ...

  10. 在Vue+element 开发中报: The template root requires exactly one elemen 错的解决和原因

    一.我正准备使用Vue + Element进行新的项目开发,然后在进行添加下一个组件时报错  二.解决及原因: 原来template中只允许模板里存在一个根节点,在 template 中添加一个 &l ...