J.U.C之AQS之 CountDownLatch、Semaphore、CyclicBarrier

J.U.C之AQS - 介绍

J.U.C(java.util.concurrent)在jdk1.5引入,引入J.U.C大大提高了java的并发性能,AQS(AbstractQueuedSynchronizer的缩写) 可以认为是J.U.C的核心,AQS 可以说是并发类中的重中之重,AQS 提供了基于firstIn,firstOut队列,这个队列可以用来构建锁或者其他相关的同步装置的基础框架
在这里插入图片描述
AQS底层是双向链表,是队列的一种实现,也可以当做队列,其中,Sync queue 是双向链表,其中包括head节点,tail节点,condition queue 不是必须的,condition queue 是个单项链表,只有当程序需要使用condition时,才会创建condition queue ,并且可能会有多个condition queue

AQS 的设计

(1)、使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架
(2)、利用了一个int类型表示状态(在AQS类中有个stack成员变量,基于AQS有个同步组件ReentrantLock,stack表示获取锁的线程数,0表线程还没有获取锁,1表示已经有线程获取了锁,大于1表示重入锁的数量;)
(3)、使用方法是继承,AQS设计是基于模板方法,使用时,需要继承AQS,并且复写其中的方法。
(4)、子类通过继承并通过实现它的方法管理其状态{acquire和release}的方法操纵状态;
(5)、可以同时实现排他锁和共享锁模式(独占、共享)(站着使用者角度,AQS功能主要分为两类:独占功能和共享功能,要么实现独占功能,要么实现共享锁功能,而不会同时使用独占和共享功能)

AQS实现思路

首先AQS内部维护了一个clh队列来管理锁,线程首先尝试获取锁,如果失败,就将当前线程已经等待的信息包装成一个Node节点,加入到同步队列syn queue 中,接着不断循环尝试获取锁,条件是当前节点head,直接后进才会尝试,如果失败就会阻塞自己,只到自己被唤醒,而当持有锁线程释放锁时,会唤醒队列中的后进线程,基于这些基础的设计和思路,jkd提供了很多基于AQS的子类,即常用的同步组件
1、CountDownLacth :是闭锁,通过计数来保证线程是否一直阻塞
2、Semaphore :可以控制同一时间并发线程的数目
3、CyclicBarrier
4、ReentrantLock
5、Condition
6、FutureTask

同步组件 - CountDownLacth

CountDownLacth 是同步辅助类,通过它可以完成类似于阻塞当前线程的功能,换句话说,就是一个线程一直处于等待状态,只到其他的线程执行的操作完成。CountDownLacth用了一个给定的计数器来进行初始化,这个计数器是原子操作,就是同一时刻只能有一个线程操作该计数器,计数器是不能被重置的,只能用一次。
在这里插入图片描述

CountDownLatch

  1. 当前计数器的值为3;
  2. 线程a 调用了await()方法后,当前线程进入等待状态awaiting;
  3. 其他线程每次执行countDown()方法时,计数器就会减一,比如:线程1调用countDown()方法,计数器值为2,然后其他不停的执行,
  4. 直到计数器为0时,线程a 才继续执行。

CountDownLatch使用场景
在某些场景中,程序执行需要等待某个条件后,才能执行后续的操作,典型的应用:并行运算,当某个处理任务很大时,可以把一个大的计算拆分为多个并行执行的子任务,当所有子任务计算都完成时,在将子任务运算结果组装成最终结果。

例子1:
在这里插入图片描述
执行结果:
在这里插入图片描述

例子2:
在这里插入图片描述
执行结果:
在这里插入图片描述

同步组件 - Semaphore

可以控制并发访问个数,Semaphore 也有两个核心方法,acquire()和release() ,acquire()是获取一个可以运行的许可,release()则是在操作完成后释放一个许可出来,Semaphore 通过同步机制,来控制同步访问的个数,

Semaphore使用场景
Semaphore常用于仅能提供有限的资源,比如连接数据库,这里可以控制连接数据库数量,以防止数据库因连接过多,导致异常
例子1:
在这里插入图片描述
执行结果:每一秒输出一段
在这里插入图片描述

例子2:
在这里插入图片描述

例子3:
在这里插入图片描述
执行结果:
在这里插入图片描述

例子:
在这里插入图片描述
执行结果:执行5次输出,后续线程丢弃
在这里插入图片描述

尝试获取许可方法:
在这里插入图片描述

tryAcquire():尝试获取1个许可,获取不到直接丢弃线程
tryAcquire(int permits): 尝试获取permits个许可,获取不到直接丢弃线程
tryAcquire(int permits, long timeout, TimeUnit unit): 尝试获取permits个许可,并设置超时时间
tryAcquire(long timeout, TimeUnit unit):尝试获取许可,并设置超时时间

同步组件 - CyclicBarrier

在这里插入图片描述

CyclicBarrier 也是同步辅助类,允许一组线程相互等待,只到到达某个公共的屏障点(common barrier point),通过CyclicBarrier 可以完成多个线程的相互等待,只有当所有线程都就绪后才能各自继续执行下面的操作,CyclicBarrier 与CountDownLatch 有些相似的地方,都是通过计数器实现。
当某个线程调用await()方法,计数器加一,当计数器的值达到了我们设置的初始值时,因调用await()方法处于等待状态的线程会被唤醒,继续执行后续操作。
由于CyclicBarrier 释放后可以重用,所以也称为循环屏障。

CyclicBarrier 使用场景
也可以用于多线程计算数据,最后合并结果的场景

CyclicBarrier 与CountDownLatch 区别
(1)、CountDownLatch 计数器只能使用一次,CyclicBarrier 计数器用reset方法重置,循环使用
(2)、CountDownLatch,描述的是一个或N个线程等待其他线程的关系(等待完成某项操作,强调完成某项操作),CyclicBarrier,各个线程之间内部相互等待的关系。(所有的线程必须全部到达栅栏位置,才继续执行,强调线程)。

例子1:
在这里插入图片描述
执行结果:
在这里插入图片描述

例子2:
在这里插入图片描述

执行结果:捕捉抛出的异常后,但是可以继续执行下面的代码
在这里插入图片描述
正常应该捕获3个异常
在这里插入图片描述

注意,如果想保证代码正常执行,一定要保证不能抛出异常,或者将异常捕获,才能继续执行后续代码

例子3:
在这里插入图片描述
执行结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/wangnanwlw/article/details/86513162
今日推荐