共计 5146 个字符,预计需要花费 13 分钟才能阅读完成。
theme: juejin
highlight: an-old-hope
作者:汤圆
集体博客:javalover.cc
前言
随着天气的逐步变热,整个人也开始浮躁不安
当然这里说的不是我,因为我是一个比拟宁静的人
讲的是隔壁的老大哥,在申斥年幼的孩子
一通呼啸过后,男人宁静了下来,孩子也哭个不停
简介
后面咱们介绍了 JUC 中的并发容器,它相当于一个同步容器的升级版,很大水平上进步了并发的性能
明天咱们来介绍 JUC 中的并发工具,它次要是通过扭转本身的状态来控制线程的执行流程;
常见的有如下几种:
- CountDownLatch:倒计时器(属于闭锁的一种实现),用来阻塞线程
- CyclicBarrier:循环栅栏,相似倒计时器,然而比他更高级,也是用来阻塞线程(只不过阻塞的形式不同,上面会具体介绍)
- Semaphore:信号量,用来管制多个线程同时拜访指定的资源,比方咱们罕用的数据库连接池
上面让咱们开始吧
文章如果有问题,欢送大家批评指正,在此谢过啦
目录
- 什么是并发工具
- 倒计数器 CountDownLatch
- 倒计数器升级版 CyclicBarrier【循环栅栏】
- 信号量 Semaphore
- 区别
注释
1. 什么是并发工具
并发工具是一组工具类,次要是用来控制线程的执行流程,比方阻塞某个线程,以期待其余线程
2. 倒计数器 CountDownLatch
从字面意思来看,就是一个倒计数门闩(shuan,打了半天 zha 就是打不进去)
艰深一点来说,就是倒计数,工夫一到,门闩就关上
注:一旦关上,就不能再合上,即这个 CountDownLatch 的状态扭转是永恒不可复原的(记住这个点,前面会有比照)
比拟官网的说法:倒计数器用来阻塞某个(某些)线程,以期待其余多个线程的工作执行实现(以这个说法为准,下面的能够用来比照参考)
上面列出 CountDownLatch 的几个办法:
- 构造方法:
public CountDownLatch(int count)
,其中 count 就是咱们所说的外部状态(当 count= 0 时,示意达到终止状态,此时会复原被阻塞的线程) - 批改状态:
public void countDown()
,该办法会递加下面的 count 状态,每执行一次,就 -1;(当 count= 0 时,示意达到终止状态,此时会复原被阻塞的线程) - 期待:
public void await()
,该办法会阻塞以后线程,直到 count 状态变为 0,才会复原执行(除非中断,此时会抛出中断异样) - 超时期待:
public boolean await(long timeout, TimeUnit unit)
,相似下面的 await, 只不过能够设置超时工夫,等过了超时工夫,还在阻塞,则间接复原 - 获取状态值 count:
public long getCount()
,获取 count 的数值,以查看还能够递加多少次(多用来调试)
模仿场景的话,这里先列举三个,必定还有其余的
- 第一个就是计数器了,最间接的
- 第二个就是统计工作执行时长
- 第三个就是多人 5V5 游戏,等所有人加载结束,就开始游戏
上面咱们以第三个场景为例,写个例子:多人游戏加载画面
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {
// 1. 结构一个倒计数器,给定一个状态值 10
CountDownLatch latch = new CountDownLatch(10);
System.out.println("筹备加载");
// 这里咱们创立 10 个线程,模仿 5V5 游戏的 10 个玩家
for (int i = 0; i < 10; i++) {new Thread(()->{
// 这里咱们给点延时,模仿网络延时
try {Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"加载 100%");
// 2. 这里的 countDown 就是用来扭转倒计数器的外部状态,每次 -1
latch.countDown(); // 这里不会阻塞以后线程,执行完后就立马返回了}).start();}
// 3. 这里阻塞期待状态的实现,即 10 变为 0;
latch.await();
System.out.println("所有人加载实现,开始游戏");
}
}
输入如下:
筹备加载
Thread- 0 加载 100%
Thread- 1 加载 100%
Thread- 2 加载 100%
Thread- 3 加载 100%
Thread- 4 加载 100%
Thread- 5 加载 100%
Thread- 6 加载 100%
Thread- 8 加载 100%
Thread- 9 加载 100%
Thread- 7 加载 100%
所有人加载实现,开始游戏
这里倒计数器的作用就是阻塞主线程,以期待其余 10 个子线程,等到都筹备好,再复原主线程
它的特点就是:一次性应用,达到终止状态后不能再扭转
3. 倒计数器升级版 CyclicBarrier【循环栅栏】
循环栅栏,相似倒计数器,也是用来阻塞线程,不过它的重点在于 循环 应用
而倒计数器只能用一次(这属于他们之间最显著的一个区别)
PS:猜想之所以叫循环栅栏,而不是循环门闩,可能是因为栅栏的作用比门闩更弱小,所以叫栅栏更适宜吧
官网说法:循环栅栏个别用来示意多个线程之间的互相期待(阻塞)
比方有 10 个线程,都要 await 期待;那要等到最初一个线程 await 时,栅栏才会关上
如果有定义栅栏动作,那么当栅栏关上时,会执行栅栏动作
栅栏动作就是:栅栏关上后需执行的动作,通过构造函数的 Runnable 参数指定,可选参数,上面会介绍
这个属于循环栅栏和倒计数器的 第二个区别:
- 循环栅栏强调的是多个被阻塞线程之间的相互协作关系(期待)
- 而倒计数器强调的是单个(或多个)线程被阻塞,来期待其余线程的工作执行
上面咱们看几个循环栅栏 CyclicBarrier 外部的办法:
- 构造方法:
public CyclicBarrier(int parties, Runnable barrierAction)
,第一个示意需期待(阻塞)的线程数,第二个 barrierAction 就是下面咱们说的栅栏动作,即当最初一个线程也被阻塞时,就会触发这个栅栏动作(这个参数可选,如果没有,则不执行任何动作) - 期待:
public int await()
,阻塞以后线程,直到最初一个线程被阻塞,才会复原 - 超时期待:
public boolean await(long timeout, TimeUnit unit)
,相似下面的 await, 只不过能够设置超时工夫 - 获取以后期待的线程数:
public int getNumberWaiting()
,即调用了 await 办法的线程数量
场景:
- 大事化小,小事合并:就是将某个大工作拆解为多个小工作,等到小工作都实现,再合并为一个后果
-
多人对战游戏团战
- 下面的倒计数器示意游戏开始前的筹备工作(只需筹备一次)
- 而这里的循环栅栏则能够示意游戏开始后的团战工作(可团战屡次)
上面看下例子:多人游戏团战画面
public class CyclicBarrierDemo {public static void main(String[] args) throws InterruptedException {
// 1. 创立一个循环栅栏,给定期待线程数 10 和栅栏动作
CyclicBarrier barrier = new CyclicBarrier(10,()->{
// 栅栏动作,等到所有线程都 await,就会触发
System.out.println("=== 人齐了,开始团吧");
try {Thread.sleep(2000);
} catch (InterruptedException e) {e.printStackTrace();
}
});
System.out.println("=== 筹备第一波团战 ===");
// 2. 创立 10 个线程,模仿 10 个玩家
for (int i = 0; i < 10; i++) {new Thread(()->{
try {
// 玩家到场
System.out.println(Thread.currentThread().getName()+"=> 第一波团,我筹备好了");
// 期待其他人,等人齐就能够团了(人齐了会执行栅栏动作,此时这边也会复原执行)barrier.await();} catch (InterruptedException e) {e.printStackTrace();
} catch (BrokenBarrierException e) {e.printStackTrace();
}
}).start();}
// 3. 查问以后期待都线程数量,如果不为 0,则主线程持续期待
while (barrier.getNumberWaiting()!=0){Thread.sleep(1000);
}
System.out.println("=== 第一波团战完结 ===");
// 4. 此时还能够进行第二波第三波团战。。。(循环栅栏可循环触发,倒计数器只能触发一次)}
}
输入如下:
=== 筹备第一波团战 ===
Thread-0=> 第一波团,我筹备好了
Thread-1=> 第一波团,我筹备好了
Thread-2=> 第一波团,我筹备好了
Thread-3=> 第一波团,我筹备好了
Thread-4=> 第一波团,我筹备好了
Thread-5=> 第一波团,我筹备好了
Thread-6=> 第一波团,我筹备好了
Thread-7=> 第一波团,我筹备好了
Thread-8=> 第一波团,我筹备好了
Thread-9=> 第一波团,我筹备好了
=== 人齐了,开始团吧
=== 第一波团战完结 ===
4. 信号量 Semaphore
信号量次要是用来管制多个线程同时拜访指定资源,比方数据库连接池,超过指定数量,就阻塞期待
上面咱们介绍下信号量的几个要害办法:
-
构造方法:
public Semaphore(int permits, boolean fair)
, 第一个参数为许可数,即容许同时拜访的的线程数,第二个参数为偏心还是非偏心模式(默认非偏心)- 偏心模式,谁先调用 acquire,谁就先拜访资源,FIFO 先进先出
- 非偏心模式,容许插队,如果某个线程刚开释了许可,另一个线程就调用了 acquire,那么这个线程就会插队拜访资源)
- 获取许可:
public void acquire()
,如果有许可,则间接返回,并将许可数递加 1;如果没可用的许可,就阻塞期待,或者被中断 - 尝试获取许可:
public boolean tryAcquire()
,相似下面的 acquire,然而不会被阻塞和中断,因为如果没有可用的许可,则间接返回 false - 开释许可:
public void release()
,开释一个许可,并将许可数递增 1 - 获取可用的许可数量:
public int availablePermits()
,这个办法个别用来调试
场景:数据库连接池
信号量的特点就是可重复使用许可,所以像数据库连接池这种场景就很适宜了
这里就不举例子了,就是多个线程 acquire 和 release,获取许可时,如果没有就阻塞,如果有就立刻返回
5 区别
用表格看比拟不便点
区别 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
可应用次数 | 单次 | 屡次(循环应用) | 屡次(循环应用) |
线程的阻塞 | 阻塞单个(多个)线程,以期待其余线程的执行 | 多个线程之间的互相阻塞 | 超过许可数,会阻塞 |
场景 | 1. 计数器 2. 统计工作执行时长 3. 多人对战游戏的开局期待 |
1. 大事化小,再合并 2. 多人对战游戏的团战 |
1. 数据库连接池 |
能够看到,倒计数器次要是用来示意单个线程期待多个线程,而循环栅栏次要是用来示意多个线程之间的互相期待
总结
- 什么是并发工具:并发工具是一组工具类,次要是用来控制线程的执行流程,比方阻塞某个线程,以期待其余线程
- 倒计数器 CountDownLatch:用来示意阻塞某个(某些)线程,以期待其余多个线程的工作执行实现
- 循环栅栏 CyclicBarrier:用来示意多个线程之间的互相期待合作(阻塞)
- 信号量 Semaphore:用来示意容许同时拜访指定资源的许可数(线程数)
- 区别:
区别 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
可应用次数 | 单次 | 屡次(循环应用) | 屡次(循环应用) |
线程的阻塞 | 阻塞单个(多个)线程,以期待其余线程的执行 | 多个线程之间的互相阻塞 | 超过许可数,会阻塞 |
场景 | 1. 计数器 2. 统计工作执行时长 3. 多人对战游戏的开局期待 |
1. 大事化小,再合并 2. 多人对战游戏的团战 |
1. 数据库连接池 |
参考内容:
- 《Java 并发编程实战》
- 《实战 Java 高并发》
后记
学习之路,真够长,共勉之
写在最初:
愿你的意中人亦是中意你之人