AQS
overview
AQS的全程为 AbstractQueuedSynchronizer
,位于java.util.concurrent.locks
。
AQS 就是一个抽象类,主要用来构建锁和同步器。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
}
AQS 为构建锁和同步器提供了一些通用功能的是实现,因此,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如 ReentrantLock
,Semaphore
,其他的诸如 ReentrantReadWriteLock
,SynchronousQueue
,FutureTask
(jdk1.7) 等等皆是基于 AQS 的。
AQS框架图:
-
上图中有颜色的为Method,无颜色的为Attribution。
-
总的来说,AQS框架共分为五层,自上而下由浅入深,从AQS对外暴露的API到底层基础数据。
-
当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。
AQS原理
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(
Craig
,Landin
,andHagersten
)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。
AQS 原理图:
AQS 使用一个 int
成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。
AQS 数据结构
AQS 基本的数据结构Node:
方法和属性值 | 含义 |
---|---|
waitStatus | 当前节点在队列中的状态 |
thread | 表示处于该节点的线程 |
prev | 前驱指针 |
predecessor | 返回前驱节点,没有的话抛出Null Pointer Exception |
nextWaiter | 指向下一个处于CONDITION状态的节点 |
next | 后继指针 |
线程两种锁的模式:
模式 | 含义 |
---|---|
SHARED | 表示线程以共享的模式等待锁 |
EXCLUSIVE | 表示线程正在以独占的方式等待锁 |
waitStatus
有下面几个枚举值:
枚举 | 含义 |
---|---|
0 | 当一个Node被初始化的时候的默认值 |
CANCELLED | 为1,表示线程获取锁的请求由于中断或超时取消了 |
CONDITION | 为-2,表示当前节点在等待condition,也就是在condition队列中(满足condition后状态被修改为0) |
PROPAGATE | 为-3,当前线程处在SHARED情况下,该字段才会使用 |
SIGNAL | 为-1,当前节点的下一个节点被阻塞(或即将被阻塞),因此当前节点释放锁或取消申请锁时必须对下一个节点发送 park 唤醒信号。为了避免竞争,acquire方法必须首先表明它需要一个signal,然后尝试acquire,然后获取失败后阻塞。 |
同步状态 state
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
//返回同步状态的当前值
protected final int getState() {
return state;
}
//设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
通过修改State字段表示的同步状态可以实现多线程的独占模式和共享模式(加锁过程)
ReentrantLock
可重入锁:某个线程已经获得某个锁,可以再次获取锁而不会出现死锁(同一个线程持有锁的前提下再次申请当前锁),synchronized 和 Lock 均为可重入锁。
可中断锁:可中断锁就是可以响应中断的锁。在Java中,synchronized不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,对于 synchronized,B只能一直阻塞等待,对于 Lock,B可以在超时后中断对共享资源的等待过程并执行其他代码。
公平锁:公平锁即尽量保证线程以请求锁的顺序来获取锁。比如,多个线程等待一个锁,当这个锁被释放时,等待时间最久的线程会获得该锁。而非公平锁则无法保证锁的获取按照请求锁的顺序进行,可能导致某个或者一些线程永远获取不到锁,synchronized 为非公平锁。
Java5可以通过显式定义同步锁对象来实现同步,由Lock对象充当同步锁。
Lock是控制多个线程对共享资源进行访问的工具。通常锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象(有些锁,如ReadWriteLock
(读写锁)允许对共享资源并发访问)。Lock、ReadWriteLock
是Java5提供的两个根接口,并为Lock提供了ReentrantLock
(可重入锁)实现类,为ReadWriteLock
提供了ReentrantReadWriteLock
实现类。
ReentrantLock
与synchronized
的区别:
demo:
// **************************Synchronized的使用方式**************************
// 1.用于代码块
synchronized (this) {}
// 2.用于对象
synchronized (object) {}
// 3.用于方法
public synchronized void test () {}
// 4.可重入
for (int i = 0; i < 100; i++) {
synchronized (this) {}
}
// **************************ReentrantLock的使用方式**************************
public void test () throw Exception {
// 1.初始化选择公平锁、非公平锁
ReentrantLock lock = new ReentrantLock(true);
// 2.可用于代码块
// 可重入性 获取两次锁
lock.lock();
try {
try {
// 3.支持多种加锁方式,比较灵活; 具有可重入特性
if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ }
} finally {
// 4.手动释放锁
lock.unlock()
}
} finally {
lock.unlock();
}
}
ReentrantLock
锁具有可重入性,也就是说,一个线程可以对已被加锁的ReentrantLock
锁再次加锁,ReentrantLock
对象会维持一个计数器来追踪lock()
方法的嵌套调用,线程在每次调用lock()
加锁后,必须显式调用unlock()
来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。
ReentrantLock
锁的构造方法中不传入参数或传入true时,产生公平锁,传入false时,产生非公平锁。
公平锁:以请求锁的顺序获得锁。
非公平锁:不保证锁的公平性,如synchronized。
Lock要求用户手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
tryLock()
与tryLock(long time, TimeUnit unit)
是比较常用的两个方法,并且体现了Lock的可中断性。
tryLock()
方法具有返回值,它表示尝试获取锁,如果获取成功,则返回true;如果获取失败(即锁已被其他线程获取),则返回false,也就是说,这个方法无论如何都会立即返回(在拿不到锁时不会一直在那等待)。
tryLock(long time, TimeUnit unit)
方法和tryLock()
方法类似,只不过这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
一般情况下,通过tryLock
来获取锁时是这样使用的:
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
//处理异常
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
Lock与synchronized的区别:
-
synchronized
是Java语言的关键字,因此是内置特性,Lock
不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。 -
synchronized
是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized
的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,Lock
是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()
放到finally{}
中 -
在资源竞争不是很激烈的情况下,
synchronized
的性能要优于ReetrantLock
,但是在资源竞争很激烈的情况下,synchronized
的性能会下降几十倍,但是ReetrantLock
的性能能维持常态。
ReentrantLock
源码分析
AQS提供了大量用于自定义同步器实现的Protected方法。自定义同步器实现的相关方法也只是为了通过修改State字段来实现多线程的独占模式或者共享模式。自定义同步器需要实现以下方法(ReentrantLock
需要实现的方法如下,并不是全部):
方法名 | 描述 |
---|---|
protected boolean isHeldExclusively() | 该线程是否正在独占资源。只有用到Condition才需要去实现它。 |
protected boolean tryAcquire(int arg) | 独占方式。arg 为获取锁的次数,尝试获取资源,成功则返回True,失败则返回False。 |
protected boolean tryRelease(int arg) | 独占方式。arg 为释放锁的次数,尝试释放资源,成功则返回True,失败则返回False。 |
protected int tryAcquireShared(int arg) | 共享方式。arg 为获取锁的次数,尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 |
protected boolean tryReleaseShared(int arg) | 共享方式。arg 为释放锁的次数,尝试释放资源,如果释放后允许唤醒后续等待结点返回True,否则返回False。 |
一般来说,自定义同步器要么是独占方式,要么是共享方式,它们也只需实现tryAcquire-tryRelease
、tryAcquireShared-tryReleaseShared
中的一种即可。AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock
。ReentrantLock
是独占锁,所以实现了tryAcquire-tryRelease
。
ReentrantLock
中公平锁和非公平锁在底层是相同的。
1. 加锁
算法伪代码:
源代码:
//根据传入参数初始化内部的sync为公平锁方式或非公平锁方式
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//调用内部类中的lock方法
public void lock() {
sync.lock();
}
非公平锁:
// java.util.concurrent.locks.ReentrantLock
static final class NonfairSync extends Sync {
...
final void lock() {
// 尝试快速CAS获取锁,失败后走正常流程,先检测state,根据state判断是否进行CAS
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
...
}
公平锁:
// java.util.concurrent.locks.ReentrantLock
static final class NonfairSync extends Sync {
...
final void lock() {
acquire(1);
}
...
}
区别在于,非公平锁会直接尝试一次插队获取锁,非公平锁获取失败后,二者都进入 acquire 方法(AQS 中的一个模板方法)。
// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先执行 tryAcquire
方法(区别在于公平锁会多一个检测当前节点是否是队首节点的检测)。
非公平锁:
// NonfairSync中
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// NonfairSync的父类Sync中
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 锁未被占用,直接CAS尝试获取锁
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;
}
公平锁中:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 锁未占用
if (c == 0) {
// 当前节点是队列中的队首节点时,CAS尝试获取锁
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;
}
当获取锁失败后,会继续执行 acquire 方法:
// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 向阻塞队列添加一个元素
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;
// CAS修改尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果pred为null,或者CAS失败(存在竞争),执行enq
enq(node);
return node;
}
//enq
private Node enq(final Node node) {
// 如果CAS失败,自旋CAS
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 队列为空(队列初始化时才会执行),将tail和head初始化为一个头节点,再插入node
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
// CAS修改尾节点
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
自旋获取锁的过程:
// 对排队中的node执行获取锁的操作,直到获取成功或发生中断
// 为true表示存在需要取消加锁的节点,仅从这段代码可以看出,
// 除非发生异常,否则不会存在需要取消加锁的节点。
final boolean acquireQueued(final Node node, int arg) {
// 标记是否成功获取到锁
boolean failed = true;
try {
/**
* interrupted表示在CLH队列的调度过程中,当前线程在休眠时,有没有被中断过
*/
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);
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
// 返回当前线程是否应该被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前驱的节点状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* 前驱节点在释放锁后会对当前节点发出signal,当前可以安全地执行park
*/
return true;
if (ws > 0) {
/*
* 如果前继节点状态为CANCELLED, 说明前驱节点已经取消,则需要通过回溯找到一个非CANCELLED状态有效节点值(值小于等于0)设置为前驱节点
* 并返回false
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 如果前继节点状态值为0或者共享锁PROPAGATE. 即前继节点为非SIGNAL或非CANCELLED, 则设置前继节点为SIGNAL,
// 意味着前驱节点释放锁后需要向当前节点发出 park 唤醒信号
// 并返回false(下一轮执行中,当前线程会被阻塞)
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 线程需要阻塞时,调用该方法挂起当前线程,然后返回当前线程的中断状态
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
2. 解锁
伪代码:
源代码:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
//如果释放锁失败,返回false
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
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);
}