一. AQS介绍
全称为AbstractQueuedSynchronizer,其主要作用是处理同步,它是很多并发锁和很多同步工具类的实现基础(例如ReenTrantLock,CountDownLatch,FutureTask等)
1.1 独享锁和共享锁的支持
ReentrantLock、 ReadWriteLock是基于AQS来实现的,这些锁没有直接继承AQS,而是定义了一个Sync类去继承AQS。为什么要这么做呢?因为锁面向的是使用用户,而同步器面向的是线程控制,那么在锁实现中聚合同步器而不是直接继承AQS就可以很好的隔离两者所关注的事情。
1.2 独享锁API
- acquire - 获取独占锁。
- acquireInterruptibly - 获取可中断的独占锁。
- tryAcquireNanos - 尝试在指定时间内获取可中断的独占锁。在以下三种情况下会返回:
-
- 在超时时间内,当前线程成功获取了锁;
- 当前线程在超时时间内被中断;
- 超时时间结束,仍未获得锁返回 false。
- release - 释放独占锁。
1.3 共享锁APi
- acquireShared - 获取共享锁。
- acquireSharedInterruptibly - 获取可中断的共享锁。
- tryAcquireSharedNanos - 尝试在指定时间内获取可中断的共享锁。
- release - 释放共享锁。
二. AQS原理
在AQS中使用了一个由volatile修饰的变量state,用来维护同步状态,状态的意义由子类赋予。
AQS维护了一个FIFO的双向链表,用来存储获取锁失败的线程。
AQS主要围绕着两种状态:获取和释放,有以下几点:
- state是独占的,还是共享的
- state被获取后,其他线程需要等待
- state被释放后,唤醒等待线程
- 线程等不及时,如何退出等待
至于线程是否可以获得state,如何释放state,不是AQS所关注的,由子类具体实现,如ReentrantLock中该状态值表示所有者线程已经重复获取该锁的次数,Semaphore中该状态值表示剩余的许可数量。
2.1 AQS的数据结构
通过阅读源码可知,AQS继承自AbstractOwnableSynchronizer
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/** 等待队列的队头,懒加载。只能通过 setHead 方法修改。 */
private transient volatile Node head;
/** 等待队列的队尾,懒加载。只能通过 enq 方法添加新的等待节点。*/
private transient volatile Node tail;
/** 同步状态 */
private volatile int state;
}
head与tail就是AQS维护的一个双向链表,每当有线程获取锁失败后,就被添加到队列末尾。
然后看一些内部链表的源码:
static final class Node {
/** 该等待同步的节点处于共享模式 */
static final Node SHARED = new Node();
/** 该等待同步的节点处于独占模式 */
static final Node EXCLUSIVE = null;
/** 线程等待状态,状态值有: 0、1、-1、-2、-3 */
volatile int waitStatus;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
/** 前驱节点 */
volatile Node prev;
/** 后继节点 */
volatile Node next;
/** 等待锁的线程 */
volatile Thread thread;
/** 和节点是否共享有关 */
Node nextWaiter;
}
其中,有个非常重要的属性:waitStatus,这是链表用于维护AQS队列中线程节点的状态,具有以下值:
CANCELLED(1):该节点的线程可能由于超时或者被中断而处于被取消的状态,一旦处在这个状态,表示这个节点应该从等待队列中移除。
SIGNAL(-1):此状态表示:后继节点会被挂起,因此在当前节点释放锁或者被取消后,必须唤醒其后继节点。
CONDITION(-2):该节点的线程处于等待条件状态,不会被当作是同步队列上的节点,直到被唤醒,设置其值为0,再重新进入阻塞状态。
PROPAGATE(-3):此状态表示下一个获取共享锁(acquireShared)应无条件传播。
0:非以上状态。
2.2 独占锁的获取和释放
2.2.1 获取
AQS使用acquire()方法获取独占锁,大致流程如下:
- 先尝试获取同步状态,如果获取同步状态成功,则结束方法,直接返回。
- 如果获取同步状态不成功,AQS会不断尝试利用CAS操作将当前线程插入等待同步队列对队尾,直到成功为止。
- 接着,不断尝试为等待队列中的线程节点获取独占锁。
2.2.2 释放
AQS使用release()方法获取独占锁,大致流程如下:
- 先尝试获取解锁线程的同步状态,如果获取同步状态不成功,结束方法,直接返回
- 如果获取同步状态成功,AQS会尝试唤醒当前线程节点的后继节点
2.2.3 获取可中断的独占锁
AQS 中使用 acquireInterruptibly()方法获取可中断的独占锁。
acquireInterruptibly实现方式相较于获取独占锁方法acquire非常相似,区别仅在于它会通过Thread.interrupted检测当前线程是否被中断,如果是,则立即抛出中断异常。
2.2.4 获取超时等待式的独占锁
AQS 中使用 tryAcquireNanos(int arg) 方法获取超时等待的独占锁。
doAcquireNanos 的实现方式 相较于获取独占锁方法acquire非常相似,区别在于它会根据超时时间和当前时间计算出截止时间。在获取锁的流程中,会不断判断是否超时,如果超时,直接返回 false;如果没超时,则用 LockSupport.parkNanos 来阻塞当前线程。
2.3 共享锁的获取和释放
2.3.1 获取共享锁
AQS中使用acquireShared() 方法获取共享锁。
acquireShared方法和acquire方法逻辑相似,区别仅在于自旋的条件以及节点出队的操作有所不同:
成功获取共享锁的条件:
tryAcquireShared方法返回值大于等于0;
当前节点的前驱节点是头节点。
2.3.2 释放共享锁
AQS 中使用 releaseShared(int arg) 方法释放共享锁。
releaseShared 首先会尝试释放同步状态,如果成功,则解锁一个或多个后继线程节点。释放共享锁和释放独享锁流程大体相似,区别在于:
对于独享模式,如果需要 SIGNAL,释放仅相当于调用头节点的 unparkSuccessor。
2.3.3 获取可中断的共享锁
AQS 中使用 acquireSharedInterruptibly(int arg) 方法获取可中断的共享锁。
acquireSharedInterruptibly 方法与 acquireInterruptibly 几乎一致,不再赘述。
2.3.4 获取超时等待式的共享锁
AQS 中使用 tryAcquireSharedNanos(int arg) 方法获取超时等待式的共享锁。
tryAcquireSharedNanos 方法与 tryAcquireNanos 几乎一致,不再赘述。