# AQS 抽象队列同步器 🔨
AQS 的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。
# 基本属性:state 双向队列
//头节点
private transient volatile Node head;
//尾节点
private transient volatile Node tail;
//状态值
private volatile int state;
1
2
3
4
5
6
2
3
4
5
6
AQS的实现依赖内部的同步队列,也就是FIFO的双向队列(双向链表),如果当前线程竞争锁失败, 那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。 当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
# 独占锁 、 共享锁
- 独占锁 : 同一时间只有一个线程能拿到锁执行,锁的状态只有0和1两种情况
- ReentrantLock
- 共享锁 : 同一时间有多个线程可以拿到锁协同工作,锁的状态大于或等于0
- CountDownLatch
# 为什么要用双向链表
- 因为处于锁阻塞的线程允许被中断,被中断的线程是不需要去竞争锁的,但是它仍然存在于双向链表里面。 在后续的锁竞争中,需要把这个节点从链表里面移除,如果是单向列表,就需要从head节点开始往下逐个便利,效率低下。
# Semaphore(信号量)
synchronized
和 ReentrantLock
都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
- Semaphore 与 CountDownLatch 一样,也是共享锁的一种实现。它默认构造 AQS 的 state 为 permits。当执行任务的线程数量超出 permits,那么多余的线程将会被放入阻塞队列 Park,并自旋判断 state 是否大于 0。
- 只有当 state 大于 0 的时候,阻塞的线程才能继续执行,此时先前执行任务的线程继续执行 release() 方法,release() 方法使得 state 的变量会加 1,那么自旋的线程便会判断成功。
- 如此,每次只有最多不超过 permits 数量的线程能自旋成功,便限制了执行任务线程的数量。
# CountDownLatch (倒计时器)
CountDownLatch允许 count 个线程去阻塞等待其他线程。实现流程如下:
- CountDownLatch 是共享锁的一种实现,它默认构造 AQS 的 state 值为 count。当线程使用 countDown() 方法时,其实使用了tryReleaseShared方法以 CAS 的操作来减少 state,直至 state 为 0 。
- 当调用 await() 方法的时候,如果 state 不为 0,那就证明任务还没有执行完毕,await() 方法就会一直阻塞,也就是说 await() 方法之后的语句不会被执行。
- 然后,CountDownLatch 会自旋 CAS 判断 state == 0,如果 state == 0 的话,就会释放所有等待的线程,await() 方法之后的语句得到执行。
两种典型的使用场景:
- 某一线程在开始运行前等待 n 个线程执行完毕
- 将 CountDownLatch 的计数器初始化为 n (new CountDownLatch(n)),每当一个任务线程执行完毕,就将计数器减 1 (countdownlatch.countDown()),当计数器的值变为 0 时,在 CountDownLatch 上 await() 的线程就会被唤醒。
- 一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
- 实现多个线程开始执行任务的最大并行性。
- 初始化一个共享的 CountDownLatch 对象,将其计数器初始化为 1 (new CountDownLatch(1)),多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。
# CyclicBarrier(循环栅栏)
它是一个同步的工具,能够允许一组线程去互相等待直到都到达了屏障,CyclicBarrier对于涉及到固定大小的线程是非常有用的,线程们必须相互等待。该屏障称之为循环屏障,是因为当等待屏障的线程被释放之后,该屏障能循环使用。
CountDownLatch 的实现是基于 AQS 的,而 CycliBarrier 是基于 ReentrantLock(ReentrantLock 也属于 AQS 同步器)和 Condition 的。