JUC工具包之加法减法计数器信号量
Posted 若曦`
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC工具包之加法减法计数器信号量相关的知识,希望对你有一定的参考价值。
1. CountDownLatch(减法计数器)
CountDownLatch 被人称作减法计数器,实际应用的时候,按照JDK文档的解释,可以有两种场景适用。
用法一是作为发令枪,一个线程拿着发令枪,下令使其他多个线程同时开始进行
用法二是观察所有线程的开始或结束时刻,比如公交车需要等所有乘客上车才能关门,或者是所有乘客下车才能关门,也就是当所有线程开始或结束了,才开始执行主线程的后续方法
补充:new CountDownLatch(N)、coutDown()、await() 必须配合起来使用,创建计数器对象的时候赋的值是多少,coutDown() 就必须执行多少次,否则计数器是没有清零的,计数器就不会停止,其他线程也无法唤醒,所以必须保证计数器清零,所以coutDown() 的调用次数必须大于构造函数的参数值。
(1) 用法一(发令枪)
使用步骤
① new CountDownLatch(1);
在一个线程中(一般是主线程),调用该方法,创建一个值为1的减法计数器
② CountDownLatch.await();
在所有其他线程的run方法开始时,调用其方法,使线程阻塞
③ CountDownLatch.countDown();
到一定时机后在主线程中下令,调用该方法(减法计数器减一),使计数器为0,所有线程同时进入执行状态
代码示例
public static void main(String[] args) {
//1.创建一个线程发令枪(值为1的计数器)
CountDownLatch startSingle = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
new Thread(()->{
System.out.println("我是"+Thread.currentThread().getName()+",正在等待下令");
try {
startSingle.await(); //2.调用计数器await()方法使其等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是"+Thread.currentThread().getName()+",开始执行");
},"线程"+(i+1)).start();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=========");
System.out.println("我是Main线程,马上开枪下令");
System.out.println("=========");
System.out.println("boom!开跑");
startSingle.countDown(); //3. 调用countDown()方法,使计数器-1,进行下令
}
(2) 用法二(记录所有线程的开始或结束时刻)
使用步骤
① new CountDownLatch(N);
在一个线程中(一般是主线程),调用该方法,创建一个值为N的减法计数器
② countDownLatch.countDown();
在各个线程的执行方法中,调用该方法(减法计数器减一),使计数器减一,记录当前线程刚开始或已结束
③ countDownLatch.await();
在主线程中调用方法,使其阻塞,等待接收计数器值为0
代码示例
static void allStartOrEnd(){
CountDownLatch allStartSingle = new CountDownLatch(5); //1. 在主线程中创建一个值为N的减法计数器
CountDownLatch allEndSingle = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(()->{
allStartSingle.countDown(); //2. 在线程的执行任务中,使减法计数器-1
System.out.println("我是"+Thread.currentThread().getName()+",开始执行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是"+Thread.currentThread().getName()+",执行完毕,已结束");
allEndSingle.countDown(); //减法计数器-1
},"线程"+(i+1)).start();
}
try {
allStartSingle.await(); //3. 使主线程阻塞,等待计数器为空
try {
Thread.sleep(50); // 主线程休眠一小会。避免打印消息比线程执行任务的打印消息快
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是主线程,观察到线程开始的计数器值为0,已知晓所有线程都已开始执行");
System.out.println("==============");
allEndSingle.await();
System.out.println("我是主线程,观察到线程结束的计数器值为0,已知晓所有线程都已结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2. CyclicBarrier(加法计数器)
加法计数器构造器可以传入两个参数
一般使用的时候,是将要定时(或者说是定值)完成的任务,放入加法计数器中,并赋予其放行任务的值,在开启其他线程使,调用cyclicBarrier.await();方法,使值+1,当值到达放行值的时候,就执行该任务
比如一辆车,除了司机最多坐4人,司机(也就是这里的加法计数器)要求坐满了才开车,每当一个人上车,人数就+1,到达4个,就开车
使用步骤
① new CyclicBarrier(N,new Runnable());
主线程中创建一个加法计数器
② cyclicBarrier.await();
一般是在其他线程执行时使计数器+1
代码示例
public static void main(String[] args) {
// 1. new CyclicBarrier(int N,new Runable()); 在主线程中创建一个加法计数器,并赋予其放行值和要执行的任务
CyclicBarrier cyclicBarrier = new CyclicBarrier(5,new Runnable() { //为了明显,没有用lamdba表达式
@Override
public void run() {
System.out.println("==========");
System.out.println("加法计数器值到了5,放行该任务,且加法计数器的值重置为0");
}
});
for (int i = 0; i < 5; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"正在执行,任务是使计数器值+1");
cyclicBarrier.await(); // 2. cyclicBarrier.await(); 使计数器+1 这里的await()方法是让加法计数器+1
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},"线程"+(i+1)).start();
}
}
3. Semaphore (信号量)
实际开发中主要使用它来完成限流操作,限制访问某些资源的线程数量
Semaphore底层其实是一个AQS(AbstractQueuedSynchronizer 抽象的队列式的同步器)
Semaphore 的使用步骤:
- 初始化
new Semaphore(int n); //n为信号量的总数量
- 获取许可
thread.aquire(); //获取信号量的许可,同时信号量-1
- 释放
thread.release(int n); //一般写在finally中,用于释放当前线程的信号,n代表释放多少个,不写则默认是0
代码示例
public static void main(String[] args) {
//初始化3个信号量
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread((()->{
try {
// 获取信号量的许可(信号量+1)
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到了车位,开始停车,获得一个信号量+++");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
System.out.println(Thread.currentThread().getName()+"离开了车位,并释放当前信号量---");
//释放信号量
semaphore.release();
}
}),String.valueOf(i)).start();
}
}
注意点:指定释放的信号量数量时
比如只改动下一行代码
semaphore.release(); //释放一个信号量
->semaphore.release(2); //释放两个信号量
当指定释放信号量的数量时,如果信号量释放到了初始化的最大值还在释放,那么此时信号量会扩容
以上是关于JUC工具包之加法减法计数器信号量的主要内容,如果未能解决你的问题,请参考以下文章
Juc21_强大的三个工具类CountDownLatch 闭锁 CyclicBarrier Semaphore
JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕( 可以覆盖上次的打印内)等待多个远程调用结束)(代码片段