【Java 并发编程】AQS
基本说明
AQS
全称AbstractQueuedSynchronizer
,中文名为抽象队列同步器 ,它是JDK
下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器(比如:信号量)的一个同步框架。
AQS
是一个抽象类,实现了主要的一部分逻辑,还有一些方法需要其子类自行取实现以达到个性化的需求。
AQS
主要做如下三件事:
-
同步状态的维护
什么是同步状态呢?其实
AQS
并不管,它只是提供了state
变量用于记录锁状态,不同的实现锁状态的意义不同,比如:是否获取到锁,锁的重入次数,获取到锁的线程的次数等等。AQS
提供了state
的变量以及获取、修改锁的方法,子类可以使用这些方法实现对同步状态的修改。 -
**同步队列(
SyncQueue
)**的维护AQS
通过CLH
双向链表 实现的同步队列(SyncQueue
)来维护线程的等待,这里的等待是获取锁的等待,比如:对于排他锁的ReentrantLock
如果A线程获取到了锁,那么其他线程在A
释放锁之前请求获取锁,ReentrantLock
为了保证同一时间只能有一个线程访问共享资源,所以A
线程后面的线程都需要进 入同步队列(SyncQueue
)。AQS
提供了对于同步队列(SyncQueue
)的入队 、出队等方法,这些是无需子类去实现的。 -
**条件队列(
ConditionQueue
)**的维护AQS
实现了类似Synchronized
的wait/notify
的机制,条件队列(ConditionQueue
)也是一个使用内部类Node
作为载体的单线链表结构的队列。
AQS
支持两种锁机制:
-
独占模式:只允许有一个线程能够访问共享资源。
-
独立模式:允许多个线程同时访问共享资源。
作为一个抽象队列同步器 这样一个基础框架,很多锁都使用了AQS
,比如:ReentranantLock
、Semaphore
、CountDownLatch
、CylicBarrier
、ReentrantReadWriteLock
等。
ReentranantLock
就是一个典型的独占锁模式。
Semaphore
、CountDownLatch
、CylicBarrier
则是使用的共享模式。
ReentrantReadWriteLock
则是同时使用了独占和共享两种模式。
同步状态的维护
AQS
中定义了一个使用volitale
修改的共享变量state
,该变量就是同步状态,或者说使用该变量实现同步状态,同时提供了针对state
变量的获取以及修改方法,源码如下:
getState
:获取state
的状态。
setState
:设置state
的状态。
compareAndSetState
:使用CAS
乐观锁机制设置state
状态。
同步队列(SyncQueue
)的维护
AQS
通过CLH
双向链表实现的**同步队列(SyncQueue
)**来维护线程的等待(区别开条件等待)。
- 当线程获取对象锁失败的时候,
AQS
会将线程组装为一个Node对象,将其加入CLH
这个**同步队列(SyncQueue
)**的尾部,同时会阻塞当前线程。 - 当前面线程持有的锁释放的时候,头节点会再次尝试获取对象锁。
CLH
**同步队列(SyncQueue
)**如下图所示:
绿色Node1
持有对与红色共享资源的锁,灰色的Node
则处于等待状态,等到Node1
的锁释放则后续线程才有机会获取到锁,访问共享资源。
为什么使用双向链表?使用双向链表是基于实际情况的,因为**同步队列(
SyncQueue
)**中会有删除中间节点的需求(比如线程请求获取锁,但是随后取消或者异常中断,此时需要删除该节点),基于这个需求,双向链表更加高效,时间复杂度低。对于链表,删除某一个节点必须知道其前驱节点,如果使用单链表则需要从头遍历链表才能找到前驱节点,时间复杂度是
O(n)
,而使用双向链表,则时间复杂度是O(1)
。
条件队列(ConditionQueue
)的维护
AQS
使用使用条件队列(ConditionQueue
)来实现线程间的等待通知 机制,AQS
的一个内部类ConditionObject
是Condition
的一个实现类,每一个Condition
包含一个等待队列,这个等待队列是一个单链表,在每一个Condition
中都会有两个指针,一个指向头节点,一个指向尾节点。
- 当调用
Condition
的await
方法的时候会阻塞线程,将线程存储到**条件队列(ConditionQueue
)**的尾部。 - 当调用
signal
方法的时候,就会唤醒**条件队列(ConditionQueue
)的头节点线程,将其移动到 同步队列(SyncQueue
)**中,等待获取锁。
**条件队列(ConditionQueue
)**的结构大致如下:
**条件队列(ConditionQueue
)**只有在独占模式下才会被访问。
为什么使用单链表?因为对于条件等待,无需删除中间节点。
同步队列(SyncQueue
)与等待队列
同步队列 (SyncQueue
)和等待队列 ()两者是配合使用的,一个锁可以包含多个Condition
实力,一个Condition
内部就包含一个条件队列(ConditionQueue
) (为什么?因为一个Condition
下可能有多个线程在等待。)
- 当一个线程调用
await
的时候,节点将从**同步队列(SyncQueue
)移动到 条件队列(ConditionQueue
)**中, - 反之,当调用
signal
或者signalAll
的时候,当前Condition
的**条件队列(ConditionQueue
)中的的线程节点就会被唤醒,将其从 条件队列(ConditionQueue
)移动到 同步队列(SyncQueue
)中,尝试去获取锁,如果获取到,那么就没问题,如果获取不到,则会将其放到 同步队列(SyncQueue
)**的队尾。
Node的结构
Node是CLH
双向链表实现的同步队列(SyncQueue
)的节点的载体,它内部包含了线程以及线程等待等一些信息。Node与线程是一对一的关系。
下图是Node的源码定义:
在Node
类中主要有如下属性:
-
模式
java/** Marker to indicate a node is waiting in shared mode */ // 表示Node里的线程以共享的模式等待锁 static final Node SHARED = new Node(); /** Marker to indicate a node is waiting in exclusive mode */ // 表示Node里的线程以独占的模式等待锁 static final Node EXCLUSIVE = null;
上述两个变量代表线程以什么模式等待锁,
SHARED
代表共享模式,EXCLUSIVE
代表独占模式。 -
等待状态 等待状态
waitStatus
是一个特别重要的属性,它代表的是当前节点在队列中的状态,主要有如下四种状态(也可以说是五种,默认是0
)。java/** waitStatus value to indicate thread has cancelled */ static final int CANCELLED = 1; /** waitStatus value to indicate successor's thread needs unparking */ static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3;
CANCELLED
:取消状态,代表线程获取锁的请求已经取消了。SIGNAL
:表示后继节点线程处于等待状态,本节点线程释放锁后,要通知后继节点获取锁。CONDITION
:线程调用await
方法,会将其设置为CONDITION
状态,并将其放入**条件队列(ConditionQueue
)**中。PROPAGATE
:当前线程处于SHARED
共享模式下,此字段才会被使用,EXCLUSIVE
排他模式下无效,表示下一个acquireShared
需要向后传播。 -
Thread对象
一个Node节点就是代表了一个线程获取锁的状态信息描述。
-
条件队列(
ConditionQueue
)的指针nextWaiter
是在通过Condition
实现等待、唤醒
的时候使用到的,在独立模式下,它指向条件队列(ConditionQueue
) ,在共享模式下则值固定等于SHARED
(因为只有独立模式下符合条件等待、唤醒的场景)。 -
同步队列(
SyncQueue
)的指针pre
、next
。
属性总结:
pre
、next
是**同步队列(SyncQueue
)**双向链表的前驱和后继指针nextWaiter
是**条件队列(ConditionQueue
)**单链表的后继指针(独立模式),在共享模式下它是一个固定值SHARED
。waitStatus
同时包含了**同步队列(SyncQueue
)和 条件队列(ConditionQueue
)**下的节点状态,0
是默认值。
AQS
源码
独占模式同步状态获取
独占是同步获取锁的方法是acquire
,源码如下:
java
// 独占模式获取锁
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 尝试以独占模式获取锁,需要子类自行实现。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 将当前线程组装为一个Node,参数是mode是获取方式(独立或者共享), 将其加入到同步队列(SyncQueue)中
private Node addWaiter(Node mode) {
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.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); // 入队方法
return 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;
}
}
}
}
// 将线程自己打断
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
逻辑:
①:首先调用tryAcquire
方法(该方法AQS
只定义并未实现,需要由子类实现其具体功能 )尝试以独占模式获取锁,如果获取到则直接返回true
,如果,如果获取锁失败,返回false
。
②:如果①中尝试获取锁失败,就会通过调用addWaiter(Node.EXCLUSIVE)
方法,该方法首先将向前线程组装为一个独占模式的Node对象,然后调用enq(node);
将节点添加到**同步队列(SyncQueue
)**的队尾(内部使用自旋锁,会一直自选,直到加入到队尾)。
③:当在②步骤中成功加入到队尾后,执行acquireQueued
方法,该方法会使得同步队列中的节点不断地在自旋判断其前驱节点是不是头节点,如果是则尝试获取同步状态,否则会阻塞节点中的线程。
流程图如下:
独占模式同步状态释放
独占模式同步状态释放使用release
方法,源码如下:
java
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 唤醒后继节点
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)
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);
}
①:调用需要子类自行首先的tryRelease
方法尝试释放同步状态。
②:如果释放成功,则继续唤醒头结点的后继节点线程。返回true
。
③:否则返回false
。
流程图如下:
共享模式同步状态获取
java
// 共享模式同步状态获取
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
// 尝试共享模式获取同步状态,需要子类自行实现
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
// 使用共享模式获取同步状态
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED); // 添加共享模式的节点
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
通过tryAcquireShared
方法尝试获取锁,如果返回值大不小于0,表示能够获得锁,如果返回值小于0,就需要通过doAcquireShared
不断自旋获得锁。
共享模式同步状态释放
共享模式同步状态释放的方法是releaseShared
,源码如下:
java
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
// 尝试释放共享模式同步状态
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
AQS实现
供子类实现的方法主要有如下五个:
这几种方法前面基本都有介绍过,本处再简单说明下:
text
tryAcquire: 独占模式获取同步状态
tryRelease: 独占模式释放同步状态
tryAcquireShared: 共享模式获取同步状态
tryReleaseShared: 共享模式释放同步状态
isHeldExclusively:独占模式下是否被当前前程独占
接下来手动实现一个独占模式的锁,如何实现呢?
- 创建一个类
MyLock
实现Lock
接口。 - 创建一个同步器类
MySync
继承AQS
,并实现tryAcquire
、tryRelease
、isHeldExclusively
方法。 - 在
MyLock
类中,创建一个MySync
类型的属性,并初始化。重写Lock
的方法,并调用MySync
中的方法。
创建一个类MyLock
实现Lock
接口
java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock {
@Override
public void lock() {
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
}
@Override
public Condition newCondition() {
return null;
}
}
创建一个同步器类MySync
继承AQS
,并实现tryAcquire
、tryRelease
、isHeldExclusively
方法。
java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock {
/**
* 自定义同步器
*/
static class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
// 使用cas尝试修改state的值为1
if (compareAndSetState(0, arg)) {
setExclusiveOwnerThread(Thread.currentThread()); // 设置当前线程拥有访问共享资源的权限
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null); // 移除权限
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
@Override
public void lock() {
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
}
@Override
public Condition newCondition() {
return null;
}
}
在MyLock
类中,创建一个MySync
类型的属性,并初始化。重写Lock
的方法,并调用MySync
中的方法。
java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock {
/**
* 自定义同步器
*/
private static class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
// 使用cas尝试修改state的值为1
if (compareAndSetState(0, arg)) {
setExclusiveOwnerThread(Thread.currentThread()); // 设置当前线程拥有访问共享资源的权限
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null); // 移除权限
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition() {
return new ConditionObject();
}
}
private final MySync sync = new MySync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(0);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
测试下:
java
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) {
MyLock myLock = new MyLock();
// 定义A线程
new Thread(() -> {
try {
myLock.lock();
System.out.println(Thread.currentThread().getName() + "加锁");
TimeUnit.SECONDS.sleep(3L);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "解锁");
myLock.unlock();
}
}, "A").start();
// 定义B线程
new Thread(() -> {
try {
myLock.lock();
System.out.println(Thread.currentThread().getName() + "加锁");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "解锁");
myLock.unlock();
}
}, "B").start();
}
}
运行结果如下图所示:
可以看到虽然A线程首先获得锁,并且等待到了三秒,B线程等A线程释放锁之后才获得锁。
AQS
的子类实现有很多,下图是一个简单的概括,图片来源网络。