源码
前言
AbstractQueuedSynchronizer简称AQS,它是实现同步器的基本组件,比如常用的ReentrantLock、Semaphore、CountDownLatch等。
AQS定义了一套多线程访问共享资源的同步模板,解决了实现同步器时涉及的大量细节问题,极大地减少了实现工作。
AQS基于FIFO(先入先出)的队列实现,将每一个线程封装成一个Node结点,并且内部维护一个state变量,通过原子更新这个状态变量来实现加锁和解锁操作。
有一些基本的方法我们需要提前了解一下:
状态
getState()
:返回同步状态setState(int newState)
:设置同步状态compareAndSetState(int expect, int update)
:使用C A S
设置同步状态isHeldExclusively()
:当前线程是否持有资源
独占资源(不响应线程中断)
tryAcquire(int arg)
:独占式获取资源,子类实现acquire(int arg)
:独占式获取资源模板tryRelease(int arg)
:独占式释放资源,子类实现release(int arg)
:独占式释放资源模板
共享资源(不响应线程中断)
tryAcquireShared(int arg)
:共享式获取资源,返回值大于等于0则表示获取成功,否则获取失败,子类实现acquireShared(int arg)
:共享式获取资源模板tryReleaseShared(int arg)
:共享式释放资源,子类实现releaseShared(int arg)
:共享式释放资源模板
内部类Node
Node是AQS的内部类,每个线程都会封装成Node结点,用于组成CLH队列、等待队列,结点中保存着代表的线程、前驱结点、后继节点以及代表的线程的状态
static final class Node {
// 标记这个节点是共享模式
static final Node SHARED = new Node();
// 标记这个节点是独占模式
static final Node EXCLUSIVE = null;
// 标记该线程已取消(当前节点处于取消状态)
static final int CANCELLED = 1;
// 标记当前结点需要唤醒他的后继节点
static final int SIGNAL = -1;
// 标记线程等待在一个条件上
static final int CONDITION = -2;
// 标记后面的共享锁需要无条件地传播(共享锁需要连续唤醒读的线程)
static final int PROPAGATE = -3;
// 当前节点保存的线程对应的状态(可选的状态:0,CANCELLED,SIGNAL,CONDITION,PROPAGATE)
// waitStatus == 0 表示当前线程是默认状态
// waitStatus > 0 表示当前线程是取消状态
// waitStatus == -1 表示当前节点是头结点的话需要唤醒后继节点
volatile int waitStatus;
// 当前节点的前驱结点
volatile Node prev;
// 当前节点的后继节点
volatile Node next;
// 当前节点表示的线程
volatile Thread thread;
// 下一个等待在条件上的节点
Node nextWaiter;
// 是否是共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
// 返回当前结点的前驱结点(没有前驱结点抛出空指针异常)
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// 结点的构造方法,无参
Node() { // Used to establish initial head or SHARED marker
}
// 结点的构造方法,当前线程,结点是共享还是独占式
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
// 结点的构造方法,当前线程,当前线程处于的状态
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
属性
我们知道AQS实现是基于一个FIFO队列实现的,下面是AQS的成员变量,包括了队列的首尾节点,还有重要的状态变量state。
状态变量state用来控制加锁和解锁,队列用来放置等待的线程
// 头结点,头结点中存储的线程是持有锁的线程(注意:阻塞队列不包含头结点,是从头结点的后继节点到尾节点,这个区间才是阻塞区间)
private transient volatile Node head;
// 尾结点
private transient volatile Node tail;
// 控制加锁解锁的状态变量
// state > 0 持有锁
// state == 0 没有任何线程持有锁
private volatile int state;
既然说到了state,那么就介绍一下与state有关的几个方法
// 获取当前state的值
protected final int getState() {
return state;
}
// 设置state的值
protected final void setState(int newState) {
state = newState;
}
// 设置state的值,利用CAS操作执行
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
成员方法
既为了可以学习AQS的成员方法内部的实现,也为了能够贯通下来,我们就从加锁和解锁的层面顺下来(毕竟我们常用的就是lock操作和unlock操作)
lock操作
位于ReentrantLock(独占式锁)的中,加锁分为两种,一种是非公平锁,一种是公平锁
非公平锁:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
公平锁:
final void lock() {
acquire(1);
}
// 我们可以看到,非公平锁只是比公平锁多了一步CAS加锁过程,我们看一下acquire()方法
acquire方法
// AQS中的acquire方法
public final void acquire(int arg) {
// 首先尝试获取锁,获取锁成功就直接返回,失败就继续向下执行
if (!tryAcquire(arg) &&
// addWaiter(Node.EXCLUSIVE), arg)将当前线程封装成Node结点入队,设置结点为独占式,
// 入队后调用acquireQueued(该方法包括挂起当前线程,
// 以及唤醒后相关的逻辑,令当前线程不断去获取资源,直到成功才停止)
// acquireQueued返回boolean类型,true:表示被中断唤醒过,false:表示没被中断唤醒过
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire方法
tryAcquire(arg)方法,同样分为公平锁和非公平锁
非公平锁:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取state值
int c = getState();
// state为0,尝试加锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// CAS成功,设置持锁线程
setExclusiveOwnerThread(current);
// 返回true表示获取锁成功
return true;
}
}
// 如果当前线程就是持锁线程,就state就加1(重入锁)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置state的值
setState(nextc);
// 返回true
return true;
}
// 如果上面都没有执行成功,返回false
return false;
}
公平锁:
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取state的值
int c = getState();
// state == 0
if (c == 0) {
// hasQueuedPredecessors()判断当前是否有等待者线程
// true -> 当前有等待者线程,当前线程需要入队,tryAcquire直接返回false代表没有竞争到锁,方法结束
// false -> 当前没有等待者线程,当前线程可以尝试竞争锁
if (!hasQueuedPredecessors() &&
// 竞争锁
compareAndSetState(0, acquires)) {
// 竞争锁成功设置持锁线程
setExclusiveOwnerThread(current);
// 返回true代表竞争锁成功
return true;
}
}
// 如果持锁线程就是当前线程,重入
else if (current == getExclusiveOwnerThread()) {
// 得到state应该设置的值
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 设置state的值
setState(nextc);
return true;
}
// 返回false代表加锁失败的情况有:
// 1. 持锁线程后有等待者线程
// 2. 当前线程不是持锁线程
return false;
}
// 当tryAcquire执行失败(竞争锁失败)就执行addWaiter方法
addWaiter
// addWaiter将当前线程添加到阻塞队列,并返回线程包装成的Node结点
private Node addWaiter(Node mode) {
// 将当前线程构造成Node结点
Node node = new Node(Thread.currentThread(), mode);
// 获取尾结点
Node pred = tail;
// 尾结点不等于null,说明队列不为空
if (pred != null) {
// 令当前节点的前驱结点等于原来尾结点
node.prev = pred;
// 自旋将当前节点设置为新的尾结点,成功就返回包装成的Node结点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 当添加到队列失败,就执行enq方法,执行到这里有两种情况:
// 1. 当前队列为空
// 2. 当前队列不为空,但是CAS设置尾结点失败,就执行enq自选入队
enq(node);
return node;
}
enq方法
// 接下来我们看看enq方法
private Node enq(final Node node) {
for (;;) {
// 获取尾结点
Node t = tail;
// 尾结点为空说明队列为空
if (t == null) { // Must initialize
// 给当前持锁线程设置一个head结点并设置尾结点为head,之后自旋入队
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 设置当前线程的前驱结点为原来尾结点
node.prev = t;
// 自旋设置尾结点
if (compareAndSetTail(t, node)) {
// 设置当前结点为成功,将原来线程的后继节点指向它,之后返回当前节点
t.next = node;
return t;
}
}
}
}
acquireQueued
// 接下里我们来看一下acquireQueued方法
// 位于AQS,真正去竞争资源的方法
// 参数 final Node node:封装当前线程的Node,且当前线程已经入对成功
// 参数 int arg:更新state的值需要用到
// 返回true:表明当前线程挂起中当前线程被中断唤醒过;返回false:表明当前线程挂起中当前线程没有被中断唤醒过
final boolean acquireQueued(final Node node, int arg) {
// 标记当前线程抢占锁是否是失败的
// 默认为true,代表抢占锁失败
boolean failed = true;
try {
// 默认当前线程没有被中断
boolean interrupted = false;
for (;;) {
// 获取当前节点的前驱结点
final Node p = node.predecessor();
// 前驱节点是头结点则当前线程尝试获取资源,head.next任何时候都有权利争夺锁
// 只有当前节点的前驱结点是头结点时才会执行往下执行tryAcquire方法
// tryAcquire(arg)返回true说明当前线程抢到锁了,那要重置头结点了
// tryAcquire(arg)返回false,说明争抢锁失败需要继续被挂起
if (p == head && tryAcquire(arg)) {
// 设置新的头结点
setHead(node);
// 设置原来头结点的后继为null,帮助垃圾回收
p.next = null; // help GC
// 当前线程获取锁的过程中没有发生异常
failed = false;
return interrupted;
}
// 判断当前线程获取锁失败后是否需要被挂起
// true:需要被挂起,执行parkAndCheckInterrupt方法
// false:不需要被挂起,自旋继续
if (shouldParkAfterFailedAcquire(p, node) &&
// parkAndCheckInterrupt():挂起当前线程,这里的挂起指的就是中断,并标记中断标记为true,并在当前线程被唤醒后返回中断标记
// 唤醒该线程的方式:
// 1. 正常唤醒:调用unpark()方法,唤醒该线程
// 2. 其他线程给当前线程一个中断挂起信号
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// failed == true,代表当前线程抢占锁失败,执行出队逻辑
if (failed)
// node结点取消线程资源竞争
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire
接下来我们来看看shouldParkAfterFailedAcquire方法
// 位于AQS中,判断当前线程获取锁资源失败后是否需要被挂起
// 返回true:代表需要被挂起;返回false:代表不需要被挂起
// 参数pred:代表当前线程封装成的结点的前驱结点
// 参数node:代表当前线程封装成的结点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取当前线程结点的状态waitStatus
// waitStatus == 0,默认状态
// waitStatus > 0,CANCELLED==1代表当前节点是取消状态
// waitStatus == -1,SIGNAL代表当前结点释放锁后需要唤醒它的后继节点
int ws = pred.waitStatus;
// 如果ws==Node.SIGNAL,说明当前节点可以唤醒它的后继节点,在acquireQueued方法中调用parkAndCheckInterrupt去park当前节点
if (ws == Node.SIGNAL)
///
return true;
// ws > 0,代表ws == 1,当前节点是取消状态
if (ws > 0) {
do {
// 找到第一个waitStatus状态小于等于0的结点
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 将前驱结点的后继节点设为当前节点,隐含着一种操作:结点的状态为CANCELLED(1)的结点会被出队
pred.next = node;
} else {
// 当前节点的前驱结点的默认状态是0,即默认这种情况
// 将当前结点的前驱结点的状态默认设置为SIGNAL,表示该节点释放锁后会唤醒它的第一个后继结点
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt方法
// 位于AQS中,挂起当前线程
parkAndCheckInterrupt方法,挂起当前线程结点
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
unlock()操作
// unlock位于ReentrantLock中
public void unlock() {
sync.release(1);
}
release
// 调用AQS的release方法,释放锁模板
public final boolean release(int arg) {
// 执行tryRelease方法释放锁
// 返回true:释放锁成功
// 返回false:释放锁失败
if (tryRelease(arg)) {
// 获取头结点
// head什么时候会被创建出来?
// 持锁线程为释放锁,且有其他线程获取锁,此时其他线程无法获取锁
// 且此时队列为空,此时获取锁线程会为持锁线程创建一个头结点,并将获取锁线程插入到头结点的后面(head的后继节点)
Node h = head;
// 头结点不为空,且waitStatus不等于0说明头结点后面一定插入过结点
if (h != null && h.waitStatus != 0)
// 唤醒后继结点
unparkSuccessor(h);
// 返回释放资源成功
return true;
}
return false;
}
tryRelease
// 这个是Sync内部类中的方法
// 返回true:当前线程已经完全释放锁
// 返回false:当前线程未完全释放锁
protected final boolean tryRelease(int releases) {
// 获取state应该变化为的值
int c = getState() - releases;
// 当前线程和持锁线程不是同一个线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// free代表释放锁是否完全成功,默认不成功
boolean free = false;
if (c == 0) {
// state为0,说明释放锁成功,设置free为true
free = true;
// 设置持锁线程为null
setExclusiveOwnerThread(null);
}
// 设置state的值
setState(c);
return free;
}
unparkSuccessor
// 唤醒后继结点
private void unparkSuccessor(Node node) {
// 获取当前节点的waitStatus
int ws = node.waitStatus;
// 当前节点的waitStatus < 0,设置waitStatus为0,改成0的原因因为当前节点已经完成唤醒后继结点的任务了
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// s是当前节点的第一个后继节点
Node s = node.next;
// 当前节点的后继节点为null或当前节点的后继节点是取消状态
// 什么情况下当前节点的第一个后继节点为null
// 1. 当前节点是尾结点
// 2. 当前节点入队未完成时
// 只有在s != null才会执行到s.waitStatus,当s.waitStatus>0时取消节点
if (s == null || s.waitStatus > 0) {
s = null;
// 寻找第一个离node最近的可以被唤醒的节点,该节点可能是null找不到
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 如果找到可以被唤醒的节点,就唤醒它,否则什么也不做
if (s != null)
LockSupport.unpark(s.thread);
}
lockInterruptibly方法
分析一下可中断的获取锁方法lockInterruptibly
// 该方法在ReentrantLock中
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
acquireInterruptibly
// 该方法在AQS中,竞争资源的方法,可以被中断
public final void acquireInterruptibly(int arg) throws InterruptedException {
// 如果当前线程的中断标记已经为true了就直接抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
/// 尝试获取锁,成功返回,失败执行doAcquireInterruptibly方法
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
doAcquireInterruptibly
// doAcquireInterruptibly方法
private void doAcquireInterruptibly(int arg)throws InterruptedException {
// 因为获取锁失败了所以将当前线程形成的结点加入队列
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
// 获取当前节点的前驱结点
final Node p = node.predecessor();
// 如果当前节点的前驱结点是头结点,那么当前节点执行tryAcquire获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
// 判断是够应该挂起后继结点
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
// 取消指定节点的竞争
cancelAcquire(node);
}
}
cancelAcquire
cancelAcquire方法
private void cancelAcquire(Node node) {
// 当前节点是空节点,直接结束方法
if (node == null)
return;
// 设置node的thread为null
node.thread = null;
// 结点的前驱结点
Node pred = node.prev;
// 获取当前未取消排队的node的前驱
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 获取当前节点的后继节点
Node predNext = pred.next;
// 标记当前节点为取消状态
node.waitStatus = Node.CANCELLED;
// 当前节点为尾结点那就设置node未取消排队的前驱结点为尾结点
if (node == tail && compareAndSetTail(node, pred)) {
// 并设置新的尾结点的后继为null
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 当前节点的未取消的前驱结点不是头结点
if (pred != head &&
// 当前节点的前驱结点的状态为唤醒它的后继节点
((ws = pred.waitStatus) == Node.SIGNAL ||
// 前驱结点状态小于等于0设置状态为唤醒它的后继节点
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
// 完成真正的出队
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
总结
(1)AQS是Java中几乎所有锁和同步器的一个基础框架;
(2)AQS中维护了一个队列,这个队列使用双链表实现,用于保存等待锁排队的线程;
(3)AQS中维护了一个状态变量,控制这个状态变量就可以实现加锁解锁操作了;
(4)基于AQS可以自己动手写一个锁,只需要实现AQS的几个方法即可。
这篇文章整理得感觉不太好,以后会重新整理一下AQS