JUC面试(七)——CountDownLatch&CyclicBarrier&Semaphore
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
CountDownLatch
概念
让一些线程阻塞直到另一些线程完成一系列操作才被唤醒
CountDownLatch主要有两个方法当一个或多个线程调用await方法时调用线程就会被阻塞。其它线程调用CountDown方法会将计数器减1调用CountDown方法的线程不会被阻塞当计数器的值变成零时因调用await方法被阻塞的线程会被唤醒继续执行
场景
现在有这样一个场景假设一个自习室里有7个人其中有一个是班长班长的主要职责就是在其它6个同学走了后关灯锁教室门然后走人因此班长是需要最后一个走的那么有什么方法能够控制班长这个线程是最后一个执行而其它线程是随机执行的
解决方案
这个时候就用到了CountDownLatch计数器了。我们一共创建6个线程然后计数器的值也设置成6
// 计数器
CountDownLatch countDownLatch = new CountDownLatch(6);
然后每次学生线程执行完就让计数器的值减1
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 上完自习离开教室");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
最后我们需要通过CountDownLatch的await方法来控制班长主线程的执行这里 countDownLatch.await()可以想成是一道墙只有当计数器的值为0的时候墙才会消失主线程才能继续往下执行
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t 班长最后关门");
不加CountDownLatch的执行结果我们发现main线程提前已经执行完成了
1 上完自习离开教室
main 班长最后关门
2 上完自习离开教室
3 上完自习离开教室
4 上完自习离开教室
5 上完自习离开教室
6 上完自习离开教室
引入CountDownLatch后的执行结果我们能够控制住main方法的执行这样能够保证前提任务的执行
2 上完自习离开教室
4 上完自习离开教室
1 上完自习离开教室
5 上完自习离开教室
6 上完自习离开教室
3 上完自习离开教室
main 班长最后关门
完整代码
import java.util.concurrent.CountDownLatch;
/**
* @author: wzq
* @create: 2020-03-15-19:03
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 计数器
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 上完自习离开教室");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t 班长最后关门");
}
}
复习枚举
CountDownLatch例子
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo2 {
public static void main(String[] args) throws InterruptedException {
// 计数器
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 国被灭");
countDownLatch.countDown();
}, CountryEnum.foreach_countryEnum(i).getRetMessage()).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t *****秦灭六国统一华夏");
System.out.println(CountryEnum.ONE);
System.out.println(CountryEnum.ONE.getRetCode());
System.out.println(CountryEnum.ONE.getRetMessage());
}
}
枚举类
/**
* Title枚举
* DescriptionONE、TWO...相当于名字其中是属性值按属性值顺序匹配
* @author WZQ
* @version 1.0.0
* @date 2020/5/7
*/
public enum CountryEnum {
ONE(1,"齐"),TWO(2,"楚"),THREE(3,"燕"),
FOUR(4,"赵"),FIVE(5,"魏"),SIX(6,"韩");
private Integer retCode;
private String retMessage;
public Integer getRetCode() {
return retCode;
}
public String getRetMessage() {
return retMessage;
}
CountryEnum(Integer retCode, String retMessage) {
this.retCode = retCode;
this.retMessage = retMessage;
}
/**
* 获取对应的值
* @param index
* @return
*/
public static CountryEnum foreach_countryEnum(int index){
CountryEnum[] countryEnums = CountryEnum.values();
for (CountryEnum countryEnum : countryEnums) {
if(index == countryEnum.getRetCode()){
return countryEnum;
}
}
return null;
}
}
CyclicBarrier
概念
和CountDownLatch相反需要集齐七颗龙珠召唤神龙。也就是做加法开始是0加到某个值的时候就执行
CyclicBarrier的字面意思就是可循环cyclic使用的屏障Barrier。它要求做的事情是让一组线程到达一个屏障也可以叫同步点时被阻塞直到最后一个线程到达屏障时屏障才会开门所有被屏障拦截的线程才会继续干活线程进入屏障通过CyclicBarrier的await方法
案例
集齐7个龙珠召唤神龙的Demo我们需要首先创建CyclicBarrier
/**
* 定义一个循环屏障参数1需要累加的值参数2 需要执行的方法
*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙");
});
然后同时编写七个线程进行龙珠收集但一个线程收集到了的时候我们需要让他执行await方法等待到7个线程全部执行完毕后我们就执行原来定义好的方法
for (int i = 0; i < 7; i++) {
final Integer tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 收集到 第" + tempInt + "颗龙珠");
try {
// 先到的被阻塞等全部线程完成后才能执行方法
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
完整代码
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* CyclicBarrier循环屏障
*
* @author: wzq
* @create: 2020-03-16-14:40
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
/**
* 定义一个循环屏障参数1需要累加的值参数2 需要执行的方法
*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙");
});
for (int i = 0; i < 7; i++) {
final Integer tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 收集到 第" + tempInt + "颗龙珠");
try {
// 先到的被阻塞等全部线程完成后才能执行方法
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
等七个线程运行完才执行System.out.println("召唤神龙");
Semaphore
信号量
概念
信号量主要用于两个目的
- 一个是用于共享资源的互斥使用
- 另一个用于并发线程数的控制
代码
我们模拟一个抢车位的场景假设一共有6个车3个停车位
那么我们首先需要定义信号量为3也就是3个停车位
/**
* 初始化一个信号量为3默认是false 非公平锁 模拟3个停车位
*/
Semaphore semaphore = new Semaphore(3, false);
然后我们模拟6辆车同时并发抢占停车位但第一个车辆抢占到停车位后信号量需要减1
// 代表一辆车已经占用了该车位
semaphore.acquire(); // 抢占
同时车辆假设需要等待3秒后释放信号量
// 每个车停3秒
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
最后车辆离开释放信号量
// 释放停车位
semaphore.release();
完整代码
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 信号量Demo
* @author: wzq
* @create: 2020-03-16-15:01
*/
public class SemaphoreDemo {
public static void main(String[] args) {
/**
* 初始化一个信号量为3默认是false 非公平锁 模拟3个停车位
*/
Semaphore semaphore = new Semaphore(3, false);
// 模拟6部车
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
// 代表一辆车已经占用了该车位
semaphore.acquire(); // 抢占
System.out.println(Thread.currentThread().getName() + "\t 抢到车位");
// 每个车停3秒
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t 停3秒离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放停车位
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
运行结果
0 抢到车位
2 抢到车位
1 抢到车位
1 停3秒离开车位
2 停3秒离开车位
0 停3秒离开车位
3 抢到车位
4 抢到车位
5 抢到车位
3 停3秒离开车位
4 停3秒离开车位
5 停3秒离开车位
看运行结果能够发现0 2 1 车辆首先抢占到了停车位然后等待3秒后离开然后后面 3 4 5 又抢到了车位