CountDownLatch应用&源码分析
CountDownLatch介绍
CountDownLatch就是JUC包下的一个工具,整个工具最核心的功能就是计数器。
如果有三个业务需要并发处理,并且需要知道三个业务全部都处理完毕了。
需要一个并发安全的计数器来操作,CountDownLatch就可以实现。
给CountDownLatch设置一个数值,可以设置为3。
每个业务处理完毕之后,执行一次countDown方法,指定的3每次在执行countDown方法时,对3进行-1。
主线程可以在业务处理时,执行await,主线程会阻塞等待任务处理完毕。
当设置的3基于countDown方法减为0之后,主线程就会被唤醒,继续处理后续业务。

当咱们的业务中,出现2个以上允许并行处理的任务,并且需要在任务都处理完毕后,再做其他处理时,可以采用CountDownLatch去实现这个功能。
CountDownLatch应用
模拟有三个任务需要并行处理,在三个任务全部处理完毕后,再执行后续操作
CountDownLatch中,执行countDown方法,代表一个任务结束,对计数器 - 1
执行await方法,代表等待计数器变为0时,再继续执行
执行await(time,unit)方法,代表等待time时长,如果计数器不为0,返回false,如果在等待期间,计数器为0,方法就返回true
一般CountDownLatch更多的是基于业务去构建,不采用成员变量。
运行结果:
CountDownLatch源码分析
保证CountDownLatch就是一个计数器,没有什么特殊的功能,查看源码也只是计数器实现的方式
发现CountDownLatch的内部类Sync继承了AQS,CountDownLatch就是基于AQS实现的计数器。
AQS就是一个state属性,以及AQS双向链表
猜测计数器的数值实现就是基于state去玩的。
主线程阻塞的方式,也是阻塞在了AQS双向链表中。
有参构造
就是构建内部类Sync,并且给AQS中的state赋值
await方法
await方法就时判断当前CountDownLatch中的state是否为0,如果为0,直接正常执行后续任务
如果不为0,以共享锁的方式,插入到AQS的双向链表,并且挂起线程
代码略
countDown方法
countDown方法本质就是对state - 1,如果state - 1后变为0,需要去AQS的链表中唤醒挂起的节点
CyclicBarrier应用&源码分析
CyclicBarrier介绍
CyclicBarrier,就是代表循环屏障
Barrier屏障:让一个或多个线程达到一个屏障点,会被阻塞。屏障点会有一个数值,当达到一个线程阻塞在屏障点时,就会对屏障点的数值进行-1操作,当屏障点数值减为0时,屏障就会打开,唤醒所有阻塞在屏障点的线程。在释放屏障点之后,可以先执行一个任务,再让所有阻塞被唤醒的线程继续之后后续任务。
Cyclic循环:所有线程被释放后,屏障点的数值可以再次被重置。
CyclicBarrier一般被称为栅栏。
CyclicBarrier是一种同步机制,允许一组线程互相等待。现成的达到屏障点其实是基于await方法在屏障点阻塞。
CyclicBarrier并没有基于AQS实现,他是基于ReentrantLock锁的机制去实现了对屏障点–,以及线程挂起的操作。(CountDownLatch本身是基于AQS,对state进行release操作后,可以-1)
CyclicBarrier没来一个线程执行await,都会对屏障数值进行-1操作,每次-1后,立即查看数值是否为0,如果为0,直接唤醒所有的互相等待线程。
CyclicBarrier对比CountDownLatch区别
- 底层实现不同。CyclicBarrier基于ReentrantLock做的。CountDownLatch直接基于AQS做的。
- 应用场景不同。CountDownLatch的计数器只能使用一次。而CyclicBarrier在计数器达到0之后,可以重置计数器。CyclicBarrier可以实现相比CountDownLatch更复杂的业务,执行业务时出现了错误,可以重置CyclicBarrier计数器,再次执行一次。
- CyclicBarrier还提供了很多其他的功能:
- 可以获取到阻塞的线程有多少;
- 在线程互相等待时,如果有等待的线程中断,可以抛出异常,避免无限等待的问题。
- CountDownLatch一般是让主线程等待,让子线程对计数器–。CyclicBarrier更多的让子线程也一起计数和等待 时出现了错误,可以重置CyclicBarrier计数器,再次执行一次。
CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再一次执行。
CyclicBarrier应用
出国旅游。
导游小姐姐需要等待所有乘客都到位后,发送护照,签证等等文件,再一起出发
比如Tom,Jack,Rose三个人组个团出门旅游
在构建CyclicBarrier可以指定barrierAction,可以选择性指定,如果指定了,那么会在barrier归0后,优先执行barrierAction任务,然后再去唤醒所有阻塞挂起的线程,并行去处理后续任务。
所有互相等待的线程,可以指定等待时间,并且在等待的过程中,如果有线程中断,所有互相的等待的线程都会被唤醒。
如果在等待期间,有线程中断了,唤醒所有线程后,CyclicBarrier无法继续使用。
如果线程中断后,需要继续使用当前的CyclicBarrier,需要调用reset方法,让CyclicBarrier重置。
如果CyclicBarrier的屏障数值到达0之后,他默认会重置屏障数值,CyclicBarrier在没有线程中断时,是可以重复使用的。
运行结果:
CyclicBarrier源码分析
CyclicBarrier的核心属性
分成两块内容去查看,首先查看CyclicBarrier的一些核心属性,然后再查看CyclicBarrier的核心方法
CyclicBarrier的有参构造
掌握构建CyclicBarrier之后,内部属性的情况
CyclicBarrier中的await方法
在CyclicBarrier中,提供了2个await方法
- 第一个是无参的方式,线程要死等,直到屏障点数值为0,或者有线程中断;
- 第二个是有参方式,传入等待的时间,要么时间到位了,要不就是直屏障点数值为0,或者有线程中断
无论是哪种await方法,核心都在于内部调用的dowait方法
dowait方法主要包含了线程互相等待的逻辑,以及屏障点数值到达0之后的操作
Semaphone应用&源码分析
Semaphore介绍
sync、ReentrantLock是互斥锁,保证一个资源同一时间只允许被一个线程访问
Semaphore(信号量)保证1个或多个资源可以被指定数量的线程同时访问
底层实现是基于AQS去做的。
Semaphore底层也是基于AQS的state属性做一个计数器的维护。state的值就代表当前共享资源的个数。如果一个线程需要获取的1或多个资源,直接查看state的标识的资源个数是否足够,如果足够的,直接对state - 1拿到当前资源。如果资源不够,当前线程就需要挂起等待。知道持有资源的线程释放资源后,会归还给Semaphore中的state属性,挂起的线程就可以被唤醒。
Semaphore也分为公平和非公平的概念。
使用场景:连接池对象就可以基础信号量去实现管理。在一些流量控制上,也可以采用信号量去实现。再比如去迪士尼或者是环球影城,每天接受的人流量是固定的,指定一个具体的人流量,可能接受10000人,每有一个人购票后,就对信号量进行–操作,如果信号量已经达到了0,或者是资源不足,此时就不能买票。
Semaphore应用
以上面环球影城每日人流量为例子去测试一下。
运行结果:
其实Semaphore整体就是对构建Semaphore时,指定的资源数的获取和释放操作:
获取资源方式:
- acquire():获取一个资源,没有资源就挂起等待,如果中断,直接抛异常
- acquire(int):获取指定个数资源,资源不够,或者没有资源就挂起等待,如果中断,直接抛异常
- tryAcquire():获取一个资源,没有资源返回false,有资源返回true
- tryAcquire(int):获取指定个数资源,没有资源返回false,有资源返回true
- tryAcquire(time,unit):获取一个资源,如果没有资源,等待time.unit,如果还没有,就返回false
- tryAcquire(int,time, unit):获取指定个数资源,如果没有资源,等待time.unit,如果还没有,就返回false
- acquireUninterruptibly():获取一个资源,没有资源就挂起等待,中断线程不结束,继续等
- acquireUninterruptibly(int):获取指定个数资源,没有资源就挂起等待,中断线程不结束,继续等
归还资源方式:
- release():归还一个资源
- release(int):归还指定个数资源
Semaphore源码分析
先查看Semaphore的整体结构,然后基于获取资源,以及归还资源的方式去查看源码
Semaphore的整体结构
Semaphore内部有3个静态内部类。
首先是向上抽取的Sync
其次还有两个Sync的子类NonFairSync以及FairSync两个静态内部类
Sync内部主要提供了一些公共的方法,并且将有参构造传入的资源个数,直接基于AQS提供setState方法设置了state属性。
NonFairSync以及FairSync区别就是tryAcquireShared方法的实现是不一样。
Semaphore的非公平的获取资源
在构建Semaphore的时候,如果只设置资源个数,默认情况下是非公平。
如果在构建Semaphore,传入了资源个数以及一个boolean时,可以选择非公平还是公平。
从非公平的acquire方法入手
首先确认默认获取资源数是1个,并且acquire是允许中断线程时,抛出异常的。获取资源的方式,就是直接用state - 需要的资源数,只要资源足够,就CAS的将state做修改。如果没有拿到锁资源,就基于共享锁的方式去将当前线程挂起在AQS双向链表中。如果基于doAcquireSharedInterruptibly拿锁成功,会做一个事情。会执行setHeadAndPropagate方法。
acquire()以及acquire(int)的方式,都是执行acquireSharedInterruptibly方法去尝试获取资源,区别只在于是否传入了需要获取的资源个数。
tryAcquire()以及tryAcquire(int因为这两种方法是直接执行tryAcquire, 只使用非公平的实现,只有非公平的情况下,才有可能在有线程排队的时候获取到资源
但是tryAcquire(int,time,unit)这种方法是正常走的AQS提供的acquire。因为这个tryAcquire可以排队一会,即便是公平锁也有可能拿到资源。这里的挂起和acquire挂起的区别仅仅是挂起的时间问题。
- acquire是一直挂起直到线程中断,或者线程被唤醒。
- tryAcquire(int,time,unit)是挂起一段时间,直到线程中断,要么线程被唤醒,要么阻塞时间到了
还有acquireUninterruptibly()以及acquireUninterruptibly(int)只是在挂起线程后,不会因为线程的中断而去抛出异常
代码略
Semaphore公平实现
公平与非公平只是差了一个方法的实现tryAcquireShared实现
这个方法的实现中,如果是公平实现,需要先查看AQS中排队的情况
代码略
Semaphore释放资源
因为信号量从头到尾都是共享锁的实现……
释放资源操作,不区分公平和非公平
AQS中PROPAGATE节点
为了更好的了解PROPAGATE节点状态的意义,优先从JDK1.5去分析一下释放资源以及排队后获取资源的后置操作
掌握JDK1.5-Semaphore执行流程图
首先查看4个线程获取信号量资源的情况

往下查看释放资源的过程会触发什么问题
首先t1释放资源,做了进一步处理

当线程3获取锁资源后,线程2再次释放资源,因为执行点问题,导致线程4无法被唤醒
分析JDK1.8的变化

代码略
8298

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



