在学习多线程的时候还是需要一些工具来加持,所以介绍一下JUC中关于并发的工具类,主要有CountDownLatch、CyclicBarrierDemo、SemaphoreDemo、Phaser、Exchanger
CountDownLatch 门闩
门闩能够使一个线程在等待另一些线程完成各自的工作后再继续执行。
通过使用计数器来实现,计数器的初始值为线程的数量,每当一个线程完成自己的任务后,通过
countDown() 方法将计数器减一,当计数器的值为 0 时,表示所有的线程都完成各自的任务,这时等待的线程就可以恢复执行接下来的任务。
主要方法说明:
- await()
导致当前线程等到锁存器计数到零,除非线程是 interrupted - boolean await(long timeout, TimeUnit unit)
使当前线程等待直到锁存器计数到零为止,除非线程为 interrupted或指定的等待时间过去 - void countDown()
减少锁存器的计数,如果计数达到零,释放所有等待的线程。 - long getCount()
返回当前计数。
构造方法:
- CountDownLatch(int count)
举个例子
public class CountDownLatchDemo {
public static void main(String[] args) {
final int personCount=6;
CountDownLatch countDownLatch=new CountDownLatch(personCount);
for (int i=1;i<=personCount;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 下课走了");
//每走一个同学就将计数器减一
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//需要等到计数器减为0才能够执行
try {
countDownLatch.await();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t 同学都走了,班长可以锁门了");
}
}
首先设置线程的数量为 6 ,循环创建线程,每执行一个则执行countDown方法将计数器减一,之后等待,直到线程计数器减为0 后才继续往下执行。
运行结果为:
CyclicBarrier 栅栏/屏障
可循环使用的屏障,让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障时通过await() 方法
屏障也被称为循环 ,因为它可以在等待的线程被释放之后重新使用
它一共有两个构造方法
-
CyclicBarrier(int parties)
当给定数量的线程等待它时,它将跳闸,并且当屏障跳闸时不执行预定义的动作。
parties 为给定数量的线程。 -
CyclicBarrier(int parties, Runnable barrierAction)
当给定数量的线程等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行
主要方法说明:
- await() 等待所有 parties已经在这个障碍上调用了 await
- await(long timeout, TimeUnit unit) 等待所有 parties已经在此屏障上调用 await 或指定的等待时间过去
举个例子:
只有集齐七龙珠才能召唤神龙
public class CyclicBarrierDemo {
public static void main(String[] args) {
final int parties=7;
CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
System.out.println("*****召唤神龙");
});
for (int i = 1; i <=7 ; i++) {
final int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 收集到龙珠" + finalI +"颗");
try {
cyclicBarrier.await();
}catch (InterruptedException e){
e.printStackTrace();
}catch (BrokenBarrierException e){
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
运行结果为:
Semaphore 信号量
信号量的值是伸缩的,一个线程离开后会立即减少,并执行下一个线程。
信号量主要用于两个目的,一个用于多个共享资源的互斥使用,另一个用于并发线程数的控制
就很像很多线程争夺一样,它的信号量就相当于资源,然后线程去抢夺,抢到了就使用,没抢到就阻塞等待
构造方法:
- Semaphore(int permits)
创建一个 Semaphore与给定数量的许可证和非公平公平设置。 - Semaphore(int permits, boolean fair)
创建一个 Semaphore与给定数量的许可证和给定的公平设置
主要方法说明:
- acquire()
从该信号量获取许可证,阻止直到可用,或线程为 interrupted - acquire(int permits)
从该信号量获取给定数量的许可证,阻止直到所有可用,否则线程为 interrupted - release()
释放许可证,将其返回到信号量。
举例说明:
public class SemaphoreDemo {
public static void main(String[] args) {
//模拟有几个资源
final int source=3;
Semaphore semaphore=new Semaphore(source);
//表示有几个线程来抢夺资源
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"\t 抢到车位");
//暂停一会线程
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t 停车三秒后离开");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
有六辆车来抢夺三个停车位,其中只有前三个线程能够 acquire 成功拿到信号量资源,并执行操作,线程睡三秒后(模拟线程执行任务)会 release 释放资源,这样另外被阻塞的线程能够获取到资源并执行。
运行结果为:
Phaser 阶段器
java多线程技术提供了Phaser工具类,Phaser表示“阶段器”,用来解决控制多个线程分阶段共同完成任务的情景问题。一个可重复使用的同步屏障,功能类似于CyclicBarrier和CountDownLatch,但支持更灵活的使用。
主要方法:
-
arrive()
抵达这个屏障,而不用等待别人到达。 -
arriveAndAwaitAdvance()
到达这个屏障,等待其他人。 -
arriveAndDeregister()
到达这个屏障并从其中注销,而无需等待别人到达。 -
awaitAdvance(int phase)
如果当前传入参数phase和getPhase()方法返回的值一样。则在屏障处等待,直到两者值不一样。不可被打中断。并不参与parties计数。 -
getArrivedParties()
返回在此屏障的当前阶段到达的已注册方的数量。 -
register()
注册到达这个屏障的线程个数
举例:
例如有这样的一个题目:5个学生一起参加考试,一共有三道题,要求所有学生到齐才能开始考试,全部同学都做完第一题,学生才能继续做第二题,全部学生做完了第二题,才能做第三题,所有学生都做完的第三题,考试才结束。分析这个题目:这是一个多线程(5个学生)分阶段问题(考试考试、第一题做完、第二题做完、第三题做完)
MyPhaser 类
public class MyPhaser extends Phaser {
protected boolean onAdvance(int phase,int registeredParties){
//在每个阶段执行完成后回调的方法
switch (phase){
case 0:
return studentArrived();
case 1:
return finishFirstExercise();
case 2:
return finishSecondExercise();
case 3:
return finishExam();
default:
return true;
}
}
private boolean finishExam() {
System.out.println("第三题所有学生做完,考试结束");
return true;
}
private boolean finishSecondExercise() {
System.out.println("第二题所有学生做完");
return false;
}
private boolean finishFirstExercise() {
System.out.println("第一题所有学生做完");
return false;
}
private boolean studentArrived() {
System.out.println("学生准备好了,学生人数:"+getRegisteredParties());
return false;
}
}
StudentTask 类
public class StudentTask implements Runnable {
private Phaser phaser;
public StudentTask(Phaser phaser) {
this.phaser = phaser;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"到达考试");
phaser.arriveAndAwaitAdvance();
System.out.println(Thread.currentThread().getName()+"做第一题时间……");
doExercise1();
System.out.println(Thread.currentThread().getName()+"做第一题完成时间……");
phaser.arriveAndAwaitAdvance();
System.out.println(Thread.currentThread().getName()+"做第二题时间……");
doExercise2();
System.out.println(Thread.currentThread().getName()+"做第二题完成时间……");
phaser.arriveAndAwaitAdvance();
System.out.println(Thread.currentThread().getName()+"做第三题时间……");
doExercise3();
System.out.println(Thread.currentThread().getName()+"做第三题完成时间……");
phaser.arriveAndAwaitAdvance();
}
private void doExercise1() {
long duration = (long) (Math.random() * 10);
try {
TimeUnit.SECONDS.sleep(duration);
}catch (InterruptedException e){
e.printStackTrace();
}
}
private void doExercise2(){
long duration=(long) (Math.random() * 10);
try {
TimeUnit.SECONDS.sleep(duration);
}catch (InterruptedException e){
e.printStackTrace();
}
}
private void doExercise3(){
long duration=(long) (Math.random() * 10);
try {
TimeUnit.SECONDS.sleep(duration);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
PhaserDemo 类
public class PhaserDemo{
public static void main(String[] args) {
MyPhaser phaser=new MyPhaser();
StudentTask[] studentTasks=new StudentTask[5];
for (int i=0;i<studentTasks.length;i++){
studentTasks[i] =new StudentTask(phaser);
phaser.register();//注册一次表示phaser维护的线程个数
}
Thread[] threads =new Thread[studentTasks.length];
for (int i=0;i<studentTasks.length;i++){
threads[i] =new Thread(studentTasks[i],"Student"+i);
threads[i].start();
}
//等待所有线程执行结束
for (int i=0;i<studentTasks.length;i++){
try {
threads[i].join();
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("Phaser has finished" + phaser.isTerminated());
}
}
运行结果为:
Exchanger 交换器
主要用于线程间交换数据
Exchanger 是 JDK 1.5 开始提供的一个用于两个工作线程之间交换数据的封装工具类,简单说就是一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据。
构造方法:
- Exchanger() 创建一个新的交换器。
方法:
-
exchange(V x)
等待另一个线程到达此交换点(除非当前线程为 interrupted),然后将给定对象传输给它,接收其对象作为回报。 -
exchange(V x, long timeout, TimeUnit unit)
等待另一个线程到达此交换点(除非当前线程为 interrupted或指定的等待时间已过),然后将给定对象传输给它,接收其对象作为回报。