JAVA并发工具类---------------(CountDownLatch和CyclicBarrier)

时间:2021-04-13 00:39:55

CountDownLatch是什么

CountDownLatch,英文翻译为倒计时锁存器,是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:

  • 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
  • 确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
  • 等待直到某个操作所有参与者都准备就绪再继续执行。

CountDownLatch有一个正数计数器,countDown()方法对计数器做减操作,await()方法等待计数器达到0。所有await的线程都会阻塞直到计数器为0或者等待线程中断或者超时。

闭锁(倒计时锁)主要用来保证完成某个任务的先决条件满足。是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。

CountDownLatch 的两种典型用法

一:某一线程在开始运行前等待n个线程执行完毕。

将 CountDownLatch 的计数器初始化为n :new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

二:实现多个线程开始执行任务的最大并行性。

注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 CountDownLatch 对象,将其计数器初始化为 1 :new CountDownLatch(1),多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。

CountDownLatch 方法示例

方法功能:模拟项目完成--3个同事做ui,做完ui给另外2个同事做前端:
主线程
package CountDownLatchTest;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* @Description:模拟项目完成--3个同事做ui,做完ui给另外2个同事做前端:
* 要求UI将图片全部画完之后 才能交付给前端
* @Param:
* @return:
* @Date: 2019/9/17
*/
public class CountDownLatchDemo { public static void main(String[] args) {
CountDownLatch UIcountDownLatch =new CountDownLatch(3);
CountDownLatch JScountDownLatch =new CountDownLatch(2); System.out.println("项目开始做了");
ExecutorService executor=Executors.newFixedThreadPool(5);
UIPeople uiPeople=new UIPeople(UIcountDownLatch);
JSPeople jsPeople=new JSPeople(JScountDownLatch);
try {
//3个UI开始工作
for (int i = 0; i <3 ; i++) {
executor.submit(uiPeople);
}
// 等待3个UI工作进行完成
UIcountDownLatch.await();
System.out.println("UI工作全部完成");
//2个前端开始工作
for (int i = 0; i <2 ; i++) {
executor.submit(jsPeople);
}
// 等待2个前端工作进行完成
JScountDownLatch.await();
System.out.println("前端工作全部完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("项目做完了");
}
}

UI工作人员类

package CountDownLatchTest;

import java.util.Random;
import java.util.concurrent.CountDownLatch; public class UIPeople implements Runnable {
private CountDownLatch latch; public UIPeople(CountDownLatch latch) {
this.latch = latch;
} @Override
public void run() {
try {
long start = System.currentTimeMillis();
Thread.sleep(new Random().nextInt(10)*1000);
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+"界面画完了用时:"+(end-start)+"ms");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//工做做完了计时器减一
latch.countDown();
}
}
}

前端工作人员

package CountDownLatchTest;

import java.util.Random;
import java.util.concurrent.CountDownLatch; /**
* @Description: 前端人员
* @Param:
* @return:
* @Date: 2019/9/17
*/
public class JSPeople implements Runnable{ private CountDownLatch latch; public JSPeople(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
long start = System.currentTimeMillis();
Thread.sleep(new Random().nextInt(10)*1000);
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+"根据图片将前端页面画完了:"+(end-start)+"ms");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//工做做完了计时器减一
latch.countDown();
}
}
}
上面示例解释

首先使用5个线程池表示5个人员,2个CountDownLatch表示前端人员和UI人员的工作,首先UI人员进行工作,因为每个人员工作时间是不一样的,但是不管他们工作时间的长短,都会停在UIcountDownLatch.await()这个方法,
等待所有的UI人员工作进行完成,而我们是怎么职到UI人员是怎么做的呢?由上面代码可知在UI成员的线程的任务里最后都执行到latch.countDown()时,在调用这个方法时主线程的计数器会减一,当主线程的countDownLatch
计数器为0时,代表所有的UI成员任务都执行成功了,然后主线程继续执行下一步,前端人员同理。

CyclicBarrier简介

CyclicBarrier,是JDK1.5的java.util.concurrent并发包中提供的一个并发工具类。

所谓Cyclic即 循环 的意思,所谓Barrier即 屏障 的意思。

所以综合起来,CyclicBarrier指的就是 循环屏障,虽然这个叫法很奇怪,但是确能很好地表示它的作用。

CyclicBarrier方法说明

——CyclicBarrier(parties)

  初始化相互等待的线程数量的构造方法。

——CyclicBarrier(parties,Runnable barrierAction)

  初始化相互等待的线程数量以及屏障线程的构造方法。

屏障线程的运行时机:等待的线程数量=parties之后,CyclicBarrier打开屏障之前。

举例:在分组计算中,每个线程负责一部分计算,最终这些线程计算结束之后,交由屏障线程进行汇总计算。

——getParties()

  获取CyclicBarrier打开屏障的线程数量,也成为方数。

——getNumberWaiting()

  获取正在CyclicBarrier上等待的线程数量。

——await()

  在CyclicBarrier上进行阻塞等待,直到发生以下情形之一:

  • 在CyclicBarrier上等待的线程数量达到parties,则所有线程被释放,继续执行。
  • 当前线程被中断,则抛出InterruptedException异常,并停止等待,继续执行。
  • 其他等待的线程被中断,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他等待的线程超时,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他线程调用CyclicBarrier.reset()方法,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。

——await(timeout,TimeUnit)

  在CyclicBarrier上进行限时的阻塞等待,直到发生以下情形之一:

  • 在CyclicBarrier上等待的线程数量达到parties,则所有线程被释放,继续执行。
  • 当前线程被中断,则抛出InterruptedException异常,并停止等待,继续执行。
  • 当前线程等待超时,则抛出TimeoutException异常,并停止等待,继续执行。
  • 其他等待的线程被中断,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他等待的线程超时,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他线程调用CyclicBarrier.reset()方法,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。

——isBroken()

  获取是否破损标志位broken的值,此值有以下几种情况:

  • CyclicBarrier初始化时,broken=false,表示屏障未破损。
  • 如果正在等待的线程被中断,则broken=true,表示屏障破损。
  • 如果正在等待的线程超时,则broken=true,表示屏障破损。
  • 如果有线程调用CyclicBarrier.reset()方法,则broken=false,表示屏障回到未破损状态。

——reset()

  使得CyclicBarrier回归初始状态,直观来看它做了两件事:

  • 如果有正在等待的线程,则会抛出BrokenBarrierException异常,且这些线程停止等待,继续执行。
  • 将是否破损标志位broken置为false。

CyclicBarrier方法练习

主线程

package CyclicBarrierTest;

import javafx.concurrent.Worker;

import java.util.concurrent.CyclicBarrier;

/**
* @Description:
* @Param:
* @return: 一个会议要等10个人都在的时候才能开始开
* @Date: 2019/9/17
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier=new CyclicBarrier(10); for (int i = 0; i < 10; i++) {
System.out.println("开始进场" + i);
People people = new People(cyclicBarrier);
new Thread(people).start();
} }
}

人员类

package CyclicBarrierTest;

import java.util.concurrent.CyclicBarrier;

public class People implements Runnable {

    private CyclicBarrier cyclicBarrier;

    public People(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
} @Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "进成功开始等待其他人,当前有"+cyclicBarrier.getNumberWaiting()+"人在等待");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "开始开会");
// 工作线程开始处理,这里用Thread.sleep()来模拟业务处理
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "开会完毕");
} catch (Exception e) {
e.printStackTrace();
} }
}

执行结果

D:\java\jdk1.8\bin\java.exe "-javaagent:D:\ideaa\IntelliJ IDEA 2018.1.2\lib\idea_rt.jar=57236:D:\ideaa\IntelliJ IDEA 2018.1.2\bin" -Dfile.encoding=UTF-8 -classpath D:\java\jdk1.8\jre\lib\charsets.jar;D:\java\jdk1.8\jre\lib\deploy.jar;D:\java\jdk1.8\jre\lib\ext\access-bridge-32.jar;D:\java\jdk1.8\jre\lib\ext\cldrdata.jar;D:\java\jdk1.8\jre\lib\ext\dnsns.jar;D:\java\jdk1.8\jre\lib\ext\jaccess.jar;D:\java\jdk1.8\jre\lib\ext\jfxrt.jar;D:\java\jdk1.8\jre\lib\ext\localedata.jar;D:\java\jdk1.8\jre\lib\ext\nashorn.jar;D:\java\jdk1.8\jre\lib\ext\sunec.jar;D:\java\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\java\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\java\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\java\jdk1.8\jre\lib\ext\zipfs.jar;D:\java\jdk1.8\jre\lib\javaws.jar;D:\java\jdk1.8\jre\lib\jce.jar;D:\java\jdk1.8\jre\lib\jfr.jar;D:\java\jdk1.8\jre\lib\jfxswt.jar;D:\java\jdk1.8\jre\lib\jsse.jar;D:\java\jdk1.8\jre\lib\management-agent.jar;D:\java\jdk1.8\jre\lib\plugin.jar;D:\java\jdk1.8\jre\lib\resources.jar;D:\java\jdk1.8\jre\lib\rt.jar;E:\gyf\target\classes CyclicBarrierTest.CyclicBarrierDemo
开始进场0
开始进场1
开始进场2
开始进场3
开始进场4
开始进场5
开始进场6
开始进场7
开始进场8
开始进场9
Thread-0进成功开始等待其他人,当前有0人在等待
Thread-4进成功开始等待其他人,当前有1人在等待
Thread-8进成功开始等待其他人,当前有2人在等待
Thread-1进成功开始等待其他人,当前有3人在等待
Thread-5进成功开始等待其他人,当前有4人在等待
Thread-9进成功开始等待其他人,当前有5人在等待
Thread-3进成功开始等待其他人,当前有6人在等待
Thread-7进成功开始等待其他人,当前有7人在等待
Thread-2进成功开始等待其他人,当前有8人在等待
Thread-6进成功开始等待其他人,当前有9人在等待
Thread-6开始开会
Thread-0开始开会
Thread-4开始开会
Thread-8开始开会
Thread-1开始开会
Thread-5开始开会
Thread-9开始开会
Thread-3开始开会
Thread-7开始开会
Thread-2开始开会
Thread-6开会完毕
Thread-7开会完毕
Thread-2开会完毕
Thread-4开会完毕
Thread-0开会完毕
Thread-3开会完毕
Thread-5开会完毕
Thread-8开会完毕
Thread-9开会完毕
Thread-1开会完毕 Process finished with exit code 0

上面是模拟10个人一起开会的情况,当第一个线程到最后第一个线程开始执行后,都会等待后续线程到达cyclicBarrier.await()时会停驶当前线程的任务,当所有线程执行到await时,并行开始进行下一布任务

CountDownLatch与CyclicBarrier的区别:

CountDownLatch是一个同步的辅助类,允许一个或多个线程,等待其他一组线程完成操作,再继续执行。 
CyclicBarrier是一个同步的辅助类,允许一组线程相互之间等待,达到一个共同点,再继续执行。 
区别:

    • CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次
    • CyclicBarrier还提供getNumberWaiting(可以获得CyclicBarrier阻塞的线程数量)、isBroken(用来知道阻塞的线程是否被中断)等方法。
    • CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。