CountDownLatch
CountDownLatch概念
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。
CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。
CountDownLatch用法
CountDownLatch主要有2个方法,当一个或者多个线程调用await方法,调用线程会被阻塞。其他线程调用countDown方法回事计数器减一(调用countDown不会使线程阻塞),当计数器为0的时候,因调用await方法被阻塞的线程会被唤醒,继续执行
-
CountDownLatch典型用法1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
-
CountDownLatch典型用法2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。
代码示例
以下一个场景,主线程需要最后执行:
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始运行");
countDownLatch.countDown();
}
}, "t" + i).start();
}
countDownLatch.await();
System.out.println("主线程最后执行-------------");
}
}
运行结果:
线程t1开始运行
线程t4开始运行
线程t2开始运行
线程t3开始运行
线程t6开始运行
线程t5开始运行
主线程最后执行-------------
从结果可以得出,CountDownLatch可以控制线程执行先后,也就是调用了countDownLatch.await()的线程直到countDown计数器为0后才可以执行。
在看以下一个案例,大家知道,秦灭六过后才一统华夏,该案例也涉及到枚举的应用:
枚举类
public enum CountryEnum {
ONE(1, "齐国"), TWO(2, "楚国"), THREE(3, "燕国"), FOUR(4, "韩国"), FIVE(5, "赵国"), SIX(6, "魏国");
private int num;
private String name;
CountryEnum(int num, String name) {
this.num = num;
this.name = name;
}
public int getNum() {
return num;
}
public String getName() {
return name;
}
public static CountryEnum getCountryEnum(int num) {
CountryEnum[] values = CountryEnum.values();
for (CountryEnum value : values) {
if (value.getNum() == num) {
return value;
}
}
return null;
}
}
测试类
public class CountDownLatchTest02 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
int tempInt = i;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(CountryEnum.getCountryEnum(tempInt).getName() + "被灭");
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println("秦灭六国,一统华夏");
}
}
运行结果:
赵国被灭
燕国被灭
齐国被灭
楚国被灭
魏国被灭
韩国被灭
秦灭六国,一统华夏
注意枚举的使用,使用CountDownLatch可以控制主线程最后执行。
CyclicBarrier
CyclicBarrier概念
CyclicBarrier的字面意思是可循环的(Cyclic)的使用屏障(Barrier)。它主要做的事情是让一组线程到达一个屏障点(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障是通过CyclicBarrier的await方法实现的。
CyclicBarrier可以使一定数量的线程反复地在屏障位置处汇集。当线程到达屏障位置时将调用await方法,这个方法将阻塞直到所有线程都到达屏障位置。如果所有线程都到达屏障位置,那么屏障将打开,此时所有的线程都将被释放。
代码示例
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开始开会");
}
}));
for (int i = 1; i <= 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "进入会议室");
// 被阻塞,等待所有的线程都进入会议室
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}, "t"+ i).start();
}
}
}
运行结果:
t1进入会议室
t4进入会议室
t5进入会议室
t3进入会议室
t2进入会议室
开始开会
从结果可以看出,当所有的线程都调用了 await方法,cyclicBarrier构造方法的线程才会执行,同时所有调用await方法的线程同时被唤醒,继续执行下面的操作
注意 :
- CyclicBarrier的构造方法,可以传入一个实现了Runnable接口的类,表示,当所有的线程都调用了await,该线程才会被被执行。
- CyclicBarrier的构造方法也可以只传入一个int类型的数字,表示当所有的线程(传入的参数数字的线程)都调用了await,所有的调用了await方法的线程都会被唤醒,继续执行下面的代码
Semaphore
Semaphore主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程的控制数量。
Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式。它相当于给线程规定一个量从而控制允许活动的线程数。
工作原理
以一个停车场是运作为例。为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。这个停车系统中,每辆车就好比一个线程,看门人就好比一个信号量,看门人限制了可以活动的线程。假如里面依然是三个车位,但是看门人改变了规则,要求每次只能停两辆车,那么一开始进入两辆车,后面得等到有车离开才能有车进入,但是得保证最多停两辆车。对于Semaphore类而言,就如同一个看门人,限制了可活动的线程数。
Semaphore主要方法
-
Semaphore(int permits):构造方法,创建具有给定许可数的计数信号量并设置为非公平信号量。 -
Semaphore(int permits,boolean fair):构造方法,当fair等于true时,创建具有给定许可数的计数信号量并设置为公平信号量。 -
void acquire():从此信号量获取一个许可前线程将一直阻塞。相当于一辆车占了一个车位。 -
void acquire(int n):从此信号量获取给定数目许可,在提供这些许可前一直将线程阻塞。比如n=2,就相当于一辆车占了两个车位。 -
void release():释放一个许可,将其返回给信号量。就如同车开走返回一个车位。 -
void release(int n):释放n个许可。 -
int availablePermits():当前可用的许可数。
代码示例
public class SemaphoreTest {
public static void main(String[] args) {
// 表示3个车位
Semaphore semaphore = new Semaphore(3);
// 创建5个线程,一个线程代表一辆车
for (int i = 1; i <= 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
// 获得许可,在获取许可前该线程将一直阻塞在此,表示抢占车位
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 占用了车位");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 停车3秒离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 表示释放一个许可,表示离开车位
semaphore.release();
}
}
}, "t"+i).start();
}
}
}
运行结果:
t1 占用了车位
t2 占用了车位
t3 占用了车位
t1 停车3秒离开车位
t2 停车3秒离开车位
t3 停车3秒离开车位
t4 占用了车位
t5 占用了车位
t4 停车3秒离开车位
t5 停车3秒离开车位
从运行结果可知,同时只能有3个线程(即车)进入停车,离开一辆车才能进来一辆车。
即semaphore.acquire()和semaphore.release()包起来的代码块,同时只能有3个线程进来,其他线程进来必须等待里面的线程执行完释放了资源,才能进入。
semaphore和synchronize的区别:
semaphore:可以控制多个线程进入,也可以是一个线程进入代码块
synchronize:只能控制一个线程计入代码块
总结:
Semaphore主要用于控制当前活动线程数目,就如同停车场系统一般,而Semaphore则相当于看守的人,用于控制总共允许停车的停车位的个数,而对于每辆车来说就如同一个线程,线程需要通过acquire()方法获取许可,而release()释放许可。如果许可数达到最大活动数,那么调用acquire()之后,便进入等待队列,等待已获得许可的线程释放许可,从而使得多线程能够合理的运行。
718

被折叠的 条评论
为什么被折叠?



