这篇文章将为大家详细讲解有关Java并发工具怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
并发工具是一组工具类,主要是用来控制线程的执行流程,比如阻塞某个线程,以等待其他线程
从字面意思来看,就是一个倒计数门闩(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个子线程,等到都准备好,再恢复主线程
它的特点就是:一次性使用,达到终止状态后不能再改变
循环栅栏,类似倒计数器,也是用来阻塞线程,不过它的重点在于循环使用
而倒计数器只能用一次(这属于他们之间最明显的一个区别)
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=>第一波团,我准备好了 === 人齐了,开始团吧 === 第一波团战结束 ===
信号量主要是用来控制多个线程同时访问指定资源,比如数据库连接池,超过指定数量,就阻塞等待
下面我们介绍下信号量的几个关键方法:
构造方法: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,获取许可时,如果没有就阻塞,如果有就立即返回
用表格看比较方便点
区别 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
可使用次数 | 单次 | 多次(循环使用) | 多次(循环使用) |
线程的阻塞 | 阻塞单个(多个)线程,以等待其他线程的执行 | 多个线程之间的相互阻塞 | 超过许可数,会阻塞 |
场景 | 1. 计数器<br />2. 统计任务执行时长<br />3. 多人对战游戏的开局等待 | 1. 大事化小,再合并<br />2. 多人对战游戏的团战 | 1. 数据库连接池 |
可以看到,倒计数器主要是用来表示单个线程等待多个线程,而循环栅栏主要是用来表示多个线程之间的相互等待
什么是并发工具:并发工具是一组工具类,主要是用来控制线程的执行流程,比如阻塞某个线程,以等待其他线程
倒计数器 CountDownLatch:用来表示阻塞某个(某些)线程,以等待其他多个线程的任务执行完成
循环栅栏 CyclicBarrier:用来表示多个线程之间的相互等待协作(阻塞)
信号量 Semaphore:用来表示允许同时访问指定资源的许可数(线程数)
区别:
区别 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
可使用次数 | 单次 | 多次(循环使用) | 多次(循环使用) |
线程的阻塞 | 阻塞单个(多个)线程,以等待其他线程的执行 | 多个线程之间的相互阻塞 | 超过许可数,会阻塞 |
场景 | 1. 计数器<br />2. 统计任务执行时长<br />3. 多人对战游戏的开局等待 | 1. 大事化小,再合并<br />2. 多人对战游戏的团战 | 1. 数据库连接池 |
关于“Java并发工具怎么用”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。