AbstractQueuedSynchronizer(AQS)详解
文章目录
什么是AQS
是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量(status)表示持有锁的状态,ReentrantLock
,CountDownLatch
,ReentrantReadWriteLock
,Semaphore
等都继承与AQS
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
锁,面向锁的使用者,定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可。
同步器,面向锁的实现者,比如Java并发大神Douglee,提出统一规 范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。
有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
抢到资源的线程直接使用办理业务,抢占不到资源的线程的必然涉及一种排队等候机制,抢占资源失败的线程继续去等待(类似办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
既然说到了排队等候机制,那么就一定 会有某种队列形成,这样的队列是什么数据结构呢?
如果共享资源被占用,就需要一定的阻 塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node) ,通过CAS、自旋以及LockSuport.park()的方式,维护state变量的状态,使并发达到同步的效果。
AQS初步
有阻塞就需要排队,实现排队必然需要队列
AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的 FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成 一个Node节点来实现锁的分配,通过CAS完成对State值的修改。
AQS内部体系架构
AQS自身有:state+CLH
AQS的同步状态State成员变量
- 零就是没人,自由状态可以办理
- 大于等于1,有人占用窗口,等着去
AQS的CLH队列
CLH队列(三个大牛的名字组成)是一个单向队列,但在这里修改为一个双向队列
内部类Node
Node的int变量:volatile int waitStatus
: Node的等待状态waitState成员变量
AQS内部基本结构
ReentrantLock解读AQS
Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的,ReentrantLock的lock/unlock都是调用sync的lock/unlock方法
public void lock() {
sync.lock();
}
Sync
继承AbstractQueuedSynchronizer
abstract static class Sync extends AbstractQueuedSynchronizer
从最简单的lock方法开始看看公平和非公平
公平锁和非公平锁
可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()
hasQueuedPredecessors是公平锁加锁时判断等待队列中是否存在有效节点的方法
对比公平锁和非公平锁的tryAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断
!hasQueuedPredecessors(),hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:
- 公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;
- 非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程在unpark),之后还是需要竞争锁(存在线程竞争的情况下)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
整个ReentrantLock加锁过程分为三个阶段:
- 尝试加锁
- 加锁失败,线程入队列
- 线程入队列后进入阻塞状态
模拟抢锁(非公平锁)
模拟三个线程(A,B,C)抢锁,A线程先抢到锁
new Thread(() -> {
System.out.println("A come in");
lock.lock();
try {TimeUnit.MINUTES.sleep(20); } catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "A").start();
new Thread(() -> {
lock.lock();
try { System.out.println("B come in"); } finally { lock.unlock(); }
}, "B").start();
new Thread(() -> {
lock.lock();
try {System.out.println("B come in"); } finally { lock.unlock(); }
}, "C").start();
A线程抢锁成功过程
A线成模拟抢锁成功
进入lock方法,进入非公平锁的lock
分析lock方法:
compareAndSetState(0, 1)
方法:CAS对比并替换,这里实际是对AbstractQueuedSynchronizer
类中的private volatile int state;
字段比较交换,当线程没有锁时,比较并交换成功,并将status
的状态设置为1,并继续执行执行setExclusiveOwnerThread(Thread.currentThread());
即设置一个当前线程的排它锁
//调用本地native方法
//stateOffset:找到state的偏移地址,比较如果与期望值相同则替换
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
B线程抢锁失败
B线程模拟抢锁失败
即compareAndSetState(0, 1)
返回false
,进入acquire(1)
方法:
public final void acquire(int arg) {
//tryAcquire(arg):抢锁
//acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 加入阻塞队列
//selfInterrupt()中断
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
分析acquire(1)
方法:
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取AQS中的state变量
int c = getState();
//如果没有线程持有锁(线程A刚执行unLock()方法),比较并交换,设置state=1,,表示抢到锁,给当前线程设置一个排它锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果是当前线程持有锁,表示当前线程重入了这把锁,state值加一
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
如果返回值是true
,则是当前线程持有的这把锁,否则返回false
当线程没有抢到锁时,就要加入到阻塞队列进行管理,以下是重难点
分析acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
方法:
先分析addWaiter(Node.EXCLUSIVE)
:
private Node addWaiter(Node mode) {
//B线程准备入队列
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
//如果不为空则重新设置当前node为尾节点(此处需要将下一步enq()方法理解,在回头看)
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
在初始状态下pred = tail=null
进入enq(node)
方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
//初始化一个头节点
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
第一次for
循环初始化一个哨兵节点
第二次循环进入else
分支,并设置B线程的状态
如果此时线程C进入则:
此时,线程已经加入到队列中了;
分析acquireQueued
方法:
final boolean acquireQueued(final Node node, int arg) {
//是否取消排队
boolean failed = true;
try {
//是否被打断
boolean interrupted = false;
for (;;) {
//循环拿到当前节点的前一个节点
final Node p = node.predecessor();
//再次调用tryAcquire(arg)方法,再次抢锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//获取失败后被park
if (shouldParkAfterFailedAcquire(p, node) &&
//park并检查是否被中断
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
即:在线程没有被中断或者取消时,他会再次抢锁,如果抢锁不成功,则调用
shouldParkAfterFailedAcquire(p, node)
:
第一次循环时:
//pred:当前线程节点的前一个节点
//node 当前线程节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//ws = 0 Node.SIGNAL = -1
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {//进入此分支
/*
* CAS将ws的值替换成Node.SIGNAL的值,即-1
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
进入compareAndSetWaitStatus(pred, ws, Node.SIGNAL)
:
第二次循环:
再次进入shouldParkAfterFailedAcquire(p, node)
:此时
ws == Node.SIGNAL
进入:返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
程序进入if
分支,并调用parkAndCheckInterrupt())
private final boolean parkAndCheckInterrupt() {
//此时程序不在抢锁,而是park住,等待被唤醒
LockSupport.park(this);
//根据park方法API的描述,三种情况会向下执行
//1: 被unpark
//2: 被中断(interrupt)
//3: 其他不合逻辑的返回才会向下执行
return Thread.interrupted();
}
此时B线程阻塞,C线程同理也在此处阻塞
如果对LockSupport不了解可以查看此处
A线程释放锁
释放锁时调用sync
的方法:
public void unlock() {
sync.release(1);
}
release
方法:释放锁成功会重设头结点
public final boolean release(int arg) {
//尝试释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//unpark并且重置head节点
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease
:尝试释放锁,当返回true
时,调用unparkSuccessor(h)
protected final boolean tryRelease(int releases) {
//因为是重入锁,只有当c=0时才算完全释放
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
//设置当前没有线程持有锁
setExclusiveOwnerThread(null);
}
//将state的状态设置成0
setState(c);
return free;
}
unparkSuccessor(h)
,h代表头结点,此时node.waitStatus
的状态是-1,将他设置为0,并唤醒其他线程
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
//
int ws = node.waitStatus;
if (ws < 0)
//设置waitStatus的值为0
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
//此时不进此方法
if (s == null || s.waitStatus > 0) {
s = 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);
}
B线程抢锁成功
B线程整在阻塞由于A线程执行LockSupport.unpark(s.thread);
后B,C线程的parkAndCheckInterrupt())
继续执行acquireQueued
循环中的方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
//继续执行返回false
return Thread.interrupted();
}
final boolean acquireQueued(final Node node, int arg) {
....
for (;;) {
//再次抢锁,tryAcquire(arg)抢锁成功,向下执行
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
}
....
}
更新队列
**更新队列:**替换B节点为新的哨兵节点,老的哨兵节点被回收
非公平锁
与公平锁不同点在于:多了一句!hasQueuedPredecessors()
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
此方法作用在于看当前线程的node是否排在头结点的后面,如果不是,则没有抢锁资格
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}