【Java并发编程篇】CountDownLatch、CyclicBarrier、Semaphore同步器原理

CountDownLatch

什么是 CountDownLatch

CountDownLatch,闭锁,就是一个基于 AQS 共享模式的同步计数器,它内部的方法都是围绕 AQS 实现的。主要作用是使一个或一组线程在其他线程执行完毕之前,一直处于等待状态,直到其他线程执行完成后再继续执行。

CountDownLatch 利用 AQS 的 state 变量充当计数器(由 volatile 修饰并使用 CAS 进行更新的),计数器的初始值就是线程的数量,每当一个线程执行完成,计数器的值就会减一,当计数器的值为 0 时,表示所有的线程都已经完成任务了,那么接下来就唤醒在 CountDownLatch 上等待的线程执行后面的任务。

那么当计数器的值为 0 时,主线程是如何被唤醒的呢?这就要从 CountDownLatch 的工作流程来说明了,CountDownLatch 的工作流程可以看成在一开始只在 CLH 队列中放入一个主线程,然后不停的唤醒,唤醒之后如果发现 state 还是不为0,则继续等待。而主线程什么时候会被唤醒呢?当每个子线程执行完毕的时候,会调用 countDown() 并基于 CAS 将计数器 state 的值减一,减一成功释放资源后,就会调用 unparkSuccessor() 唤醒主线程,当所有的子线程都执行完了,也就是 state 为 0 时,这时候主线程被唤醒之后就可以继续执行了。

state 被减成了 0 之后,就无法继续使用这个 CountDownLatch 了,需要重新 new 一个,因为 state 的数量只有在初始化 CountDownLatch 的时候才可以设置,这也是 CountDownLatch 不可重用的原因。

CyclicBarrier

什么是CyclicBarrier

CyclicBarrier,循环栅栏,通过 CyclicBarrier 可以实现一组线程之间的相互等待,当所有线程都到达屏障点之后再执行后续的操作。通过 await() 方法可以实现等待,当最后一个线程执行完,会使得所有在相应 CyclicBarrier 实例上等待的线程被唤醒,而最后一个线程自身不会被暂停。

CyclicBarrier 没有像 CountDownLatch 和 ReentrantLock 使用 AQS 的 state 变量,它是直接借助 ReentrantLock 加上 Condition 等待唤醒的功能进而实现的。在构建 CyclicBarrier 的时候,传入的值会赋值给 CyclicBarrier 内部维护的变量 count,同时也会赋值给 parties 变量(这是可以复用的关键)。

线程调用 await() 表示线程已经到达栅栏,每次调用 await() 时,会将 count 减一,操作 count 值是直接使用 ReentrantLock 来保证线程安全性的,如果 count 不为 0,则添加到 condition 队列中,如果 count 等于 0,则把节点从 condition 队列中移除并添加到 AQS 队列中进行全部唤醒,并且将 parties 的值重新赋值给 count 从而实现复用。

Semaphore

什么是 Semaphore

Semaphore 信号量,主要用于控制并发访问共享资源的线程数量,底层基于 AQS 共享模式,并依赖 AQS 的变量 state 作为许可证 permit,通过控制许可证的数量,来保证线程之间的配合。线程使用 acquire() 获取访问许可,只有拿到 “许可证” 后才能继续运行,当 Semaphore 的 permit 不为 0 的时候,对请求资源的线程放行,同时 permit 的值减1,当 permit 的值为 0 时,那么请求资源的线程会被阻塞直到其他线程释放访问许可,当线程对共享资源操作完成后,使用 release() 归还访问许可。不同于 CyclicBarrier 和 ReentrantLock,Semaphore 不会使用到 AQS 的 Condition 条件队列,都是在 CLH 同步队列中操作,只是当前线程会被 park。另外 Semaphore 是不可重入的。

Semaphore 的公平和非公平两种模式

Semaphore 通过自定义两种不同的同步器(FairSync 和 NonfairSync)提供了公平和非公平两种工作模式,两种模式下分别提供了限时/不限时、响应中断/不响应中断的获取资源的方法(限时获取总是及时响应中断的),而所有的释放资源的 release() 操作是统一的。

  • 公平模式:遵循 FIFO,调用 acquire() 方法获取许可证的顺序时,先判断同步队列中是不是存在其他的等待线程,如果存在就将请求线程封装成 Node 结点加入同步队列,从而保证每个线程获取同步状态都是按照先到先得的顺序执行的,否则对 state 值进行减操作并返回剩下的信号量
  • 非公平模式:是抢占式的,通过竞争的方式获取,不管同步队列中是否存在等待线程,有可能一个新的获取线程恰好在一个许可证释放时得到了这个许可证,而前面还有等待的线程。

 

参考:

 

posted @ 2021-12-22 21:42  残城碎梦  阅读(37)  评论(0编辑  收藏  举报