一 CountDownLatch
倒数器:当指定值减到0,指定线程开始执行。
CountDownLatch类可以设置一个计数器,让一些线程阻塞直到另外一些线程完成后才被唤醒。
CountDownLatch主要有两个方法,当一个或多个线程调用CountDownLatch.await()方法时,调用线程会被阻塞。其他线程调用countDown方法计数器减一(调用CountDownLatch.countDown()方法时线程不会阻塞),当计数器的值变为0,调用await方法被阻塞的线程才会被唤醒,继续执行。
1.1 案例1:教室关灯
public class CountDownLatchDemo1 {
public static void main(String[] args) throws Exception {
closeDoor();
}
/**
* 关门案例
*/
private static void closeDoor() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6); // 定义了6次,需要删减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(); // 线程阻塞,直到计数减为0
System.out.println(Thread.currentThread().getName() + "\t班长锁门离开教室");
}
}
1.2 案例2:秦灭六国
public class CountDownLatchDemo2 {
public static void main(String[] args) throws Exception {
sixCountry();
}
/**
* 秦灭六国 一统华夏
*/
private static void sixCountry() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "国,灭亡");
countDownLatch.countDown();
}, CountryEnum.forEach(i).getName()).start();
}
countDownLatch.await();
System.out.println("秦统一");
}
}
enum CountryEnum {
ONE(1, "齐"),
TWO(2, "楚"),
THREE(3, "燕"),
FOUR(4, "赵"),
FIVE(5, "魏"),
SIX(6, "韩");
CountryEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
@Getter
private Integer code;
@Getter
private String name;
public static CountryEnum forEach(int index) {
CountryEnum[] countryEnums = CountryEnum.values();
for (CountryEnum countryEnum : countryEnums) {
if (index == countryEnum.getCode()) {
return countryEnum;
}
}
return null;
}
}
1.3 案例3:倒计时
public static void test1() {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据1初始化");
countDownLatch.countDown();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据2初始化");
countDownLatch.countDown();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据3初始化");
countDownLatch.countDown();
}).start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据全部完成");
}
二 CyclicBarrier
计数器:当线程运行数达到一定数量,就开放屏障
CyclicBarrier的字面意思是可循环(Cyclic) 使用的屏障(barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。
当拥有指定数量的线程执行了 cyclicBarrier.await 方法时,cyclicBarrier中的线程和其他线程才会继续执行。
2.1 案例:召唤神龙
当只有凑齐 七 颗龙珠,才能最后成功召唤神龙
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
// 当所有的线程达到屏障,才会执行当前方法
System.out.println("召唤神龙");
});
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 收集到第" + temp + "颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
三 Semaphore
信号量(Semaphore)和CountDownLatch一样,内部维护一个核心属性sync,通过AQS的共享锁机制实现。在内部维护着指定数量的许可证,并根据许可证数量进行颁发给尝试获取它的线程
信号量的主要用户两个目的:
1)一个是用于多个共享资源的相互排斥使用
2)另一个用于并发资源数的控制
3.1 核心方法
- Semaphore(int premits,boolean fair): 构造器方法。permits为信号量初始化数量,第二个参数fair可以设置是否需要公平策略,如果传入true,那么Semaphore会把等待的线程放入FIFO队列中,以便许可证被释放后,可以分配给等待时间最长的线程
- Semaphore(int n):构造器,并初始化设置许可证数量。
- acquire(): 试图获取许可证,如果当前没有可用的,就会进入阻塞等待状态
- tryAcquire(): 试图获取许可证,如果是否能够获取,都不会进入阻塞
- tryAcquire(long timeout, TimeUnit unit): 和tryAcquire一样,只是多了一个超时时间,等待指定时间还获取不到许可证,就会停止等待
- availablePermits():返回此信号量当前可用的许可证数量。此方法常用于调试和测试目的。
- release(): 释放一个许可证
- release(int permits): 释放指定数量的许可证。
3.2 案例:抢车位
public class SemaphoreDemo {
public static void main(String[] args) {
// 1、模拟3个停车位
Semaphore semaphore = new Semaphore(3);
// 模拟6部汽车
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
// 2、抢到资源
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 停3秒离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 3、释放资源
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}