这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战
往期推荐
一、🎈CountDownLatch(减法计数器)
1.1 概述
CountDownLatch是一个同步辅助类,允许一个或多个线程等待,一直到其他线程执行的操作完成后再执行。
CountDownLatch
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当有一个线程执行完毕后,然后通过 CountDown
方法来让计数器的值-1,当计数器的值为0时,表示所有线程都执行完毕,然后继续执行 await
方法 之后的语句,即在锁上等待的线程就可以恢复工作了。
1.2 类的内部类
CountDownLatch
类存在一个内部类Sync,继承自AbstractQueuedSynchronizer
,其源代码如下。
1 | js复制代码private static final class Sync extends AbstractQueuedSynchronizer { |
1.3 类的构造函数
1 | js复制代码public CountDownLatch(int count) { |
1.4 CountDownLatch两个核心函数
1.4.1 countDown
- 递减锁存器的计数,如果计数达到零,则释放所有等待的线程。
- 如果当前计数大于零,则递减。 如果新计数为零,则为线程调度目的重新启用所有等待线程。
- 如果当前计数为零,则什么也不会发生。
1 | js复制代码public void countDown() { |
releaseShared
源码如下
1 | js复制代码public final boolean releaseShared(int arg) { |
tryReleaseShared
源码如下
1 | js复制代码protected boolean tryReleaseShared(int releases) { |
- AQS的
doReleaseShared
的源码如下
1 | js复制代码private void doReleaseShared() { |
CountDownLatch的countDown
调用链:
1.4.2 await
- 使当前线程等待直到闩锁倒计时为零,除非线程被中断。
- 如果当前计数为零,则此方法立即返回。即await 方法阻塞的线程会被唤醒,继续执行。
- 如果当前计数大于零,则当前线程出于线程调度目的而被禁用并处于休眠状态。
1 | js复制代码public void await() throws InterruptedException { |
acquireSharedInterruptibly
源码如下:
1 | js复制代码public final void acquireSharedInterruptibly(int arg) |
tryAcquireShared
函数的源码如下:
1 | js复制代码protected int tryAcquireShared(int acquires) { |
doAcquireSharedInterruptibly
函数的源码如下:
1 | js复制代码private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { |
setHeadAndPropagate
方法源码如下:
1 | js复制代码private void setHeadAndPropagate(Node node, int propagate) { |
doReleaseShared
方法源码如下:
1 | js复制代码private void doReleaseShared() { |
CountDownLatch的await
调用链:
1.5 CountDownLatch示例
1 | js复制代码import java.util.concurrent.CountDownLatch; |
二、🎀CyclicBarrier(加法计数器)
2.1 概述
CyclicBarrier
也叫循环栅栏,是一个可循环利用的屏障。通过它可以实现让一组线程等待至某个状态之后再全部同时执行。每个线程在到达栅栏的时候都会调用await()方法将自己阻塞,此时计数器会减1,当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒。叫做循环是因为当所有等待线程都被释放以后,CyclicBarrier
还可以被重用(调用CyclicBarrier
的reset()方法)。
2.2 CyclicBarrier
主要方法:
1 | js复制代码public class CyclicBarrier { |
2.3 构造函数
CyclicBarrier(int, Runnable)
型构造函数
1 | js复制代码public CyclicBarrier(int parties, Runnable barrierAction) { |
CyclicBarrier(int)
型构造函数
1 | js复制代码public CyclicBarrier(int parties) { |
2.4 CyclicBarrier两个核心函数
2.4.1 dowait
1 | js复制代码private int dowait(boolean timed, long nanos) |
dowait
方法的逻辑流程:
2.4.2 nextGeneration
此函数在所有线程进入屏障后会被调用,即生成下一个版本,所有线程又可以重新进入到屏障中,其源代码如下:
1 | js复制代码private void nextGeneration() { |
在此函数中会调用AQS的
signalAll
方法,即唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。其源代码如下:
1 | js复制代码public final void signalAll() { |
doSignalAll
函数源码如下:
1 | js复制代码private void doSignalAll(Node first) { |
transferForSignal
函数源码如下:
1 | js复制代码final boolean transferForSignal(Node node) { |
enq
函数源码如下:
1 | js复制代码private Node enq(final Node node) { |
newGeneration
函数调用链:
2.5 CyclicBarrier示例
1 | js复制代码public class CyclicBarrierDemo { |
三、🩰Semaphore( 信号灯)
3.1 概述
Semaphore:信号量通常用于限制可以访问某些(物理或逻辑)资源的线程数。
使用场景:
限制资源,如抢位置、限流等。
3.2 Semaphore常用方法
void acquire()
许可数-1,从该信号量获取许可证,阻塞直到可用或线程被中断。void acquire(int permits)
许可数 - permits,从该信号量获取给定数量的许可证,阻塞直到可用或线程被中断。int availablePermits()
返回此信号量中当前可用的许可数。void release()
许可数+1,释放许可证,将其返回到信号量。void release(int permits)
许可数+permits,释放给定数量的许可证,将其返回到信号量。boolean hasQueuedThreads(
) 查询是否有线程正在等待获取许可。int getQueueLength()
返回等待获取许可的线程数的估计。
3.3 Semaphore构造函数
Semaphore(int)
型构造函数
1 | js复制代码public Semaphore(int permits) { |
Semaphore(int, boolean)
型构造函数
1 | js复制代码public Semaphore(int permits, boolean fair) { |
3.4 Semaphore两个核心函数
3.4.1 acquire
此方法从信号量获取一个(多个)许可,在提供一个许可前一直将线程阻塞,或者线程被中断,其源码如下:
1 | js复制代码public void acquire() throws InterruptedException { |
3.4.2 release
此方法释放一个(多个)许可,将其返回给信号量,源码如下:
1 | js复制代码public void release() { |
3.5 Semaphore示例
1 | js复制代码/** |
四、 🏏简单讲述 | Phaser & Exchanger
4.1 Phaser
Phaser一种可重用的同步屏障,功能上类似于CyclicBarrier和CountDownLatch,但使用上更为灵活。非常适用于在多线程环境下同步协调分阶段计算任务
(Fork/Join框架中的子任务之间需同步时,优先使用Phaser)
- 函数列表:
1 | js复制代码//构造方法 |
- 示例:
1 | js复制代码public class PhaserDemo { |
4.2 Exchanger
Exchanger允许两个线程在某个汇合点交换对象,在某些管道设计时比较有用。
Exchanger提供了一个同步点,在这个同步点,一对线程可以交换数据。每个线程通过exchange()方法的入口提供数据给他的伙伴线程,并接收他的伙伴线程提供的数据并返回。
当两个线程通过Exchanger交换了对象,这个交换对于两个线程来说都是安全的。Exchanger可以认为是SynchronousQueue
的双向形式,在运用到遗传算法和管道设计的应用中比较有用。
- Exchanger实现机制
1 | js复制代码for (;;) { |
- Exchanger示例
来一个非常经典的并发问题:你有相同的数据buffer,一个或多个数据生产者,和一个或多个数据消费者。只是Exchange类只能同步2个线程,所以你只能在你的生产者和消费者问题中只有一个生产者和一个消费者时使用这个类。
1 | jspublic复制代码 static class Producer extends Thread { |
五、CyclicBarrier与CountDownLatch的区别
CountDownLatch
的await()方法阻塞的是主线程或调用await()的线程,而CyclicBarrier
的await()阻塞的是任务线程,主线程或调用线程不受影响。CountDownLatch
无法重置计数次数,而CyclicBarrier
可以通过reset()方法来重用CountDownLatch
和CyclicBarrier
都是用作多线程同步,CountDownLatch
基于AQS,CyclicBarrier
基于ReentrantLock和Condition。
参考:《Java并发编程的艺术》
JUC系列(七)| JUC三大常用工具类CountDownLatch、CyclicBarrier、Semaphore
本文转载自: 掘金