队列同步器AQS
队列同步器概念
队列同步器(AbstractQueuedSynchronizer),简称同步器或AQS。它为实现锁提供了一个框架,内部维护了一个先进先出的队列,以及state状态变量!ReentrantLock、ReentrantReadWriteLock、Semaphore(信号量)、CountDownLatch、公平锁、非公平锁、ThreadPoolExecutor等都和AQS有关!

AQS是用来构建锁或其他同步组件的基础框架:
-
使用一个
volatile的int类型的成员变量,来表示同步状态(private volatile int state;);-
当
state=0时,则说明没有任何线程占有共享资源的锁;当state=1时,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。 -
通过
CAS完成对State值的修改,来实现多线程的独占模式(0-1)和共享模式(0-N)。
-
-
AQS通过内部类Node构成
FIFO的同步队列来完成线程获取锁的排队工作; -
同时利用内部类
ConditionObject构建等待队列,当Condition调用wait()方法后,线程将会加入等待队列中;而当Condition调用signal()方法后,线程将从等待队列转移到同步队列中进行锁竞争。
注意,这里涉及到两种队列:
-
一种的
同步队列,当线程请求锁而等待后,将加入同步队列等待; -
另一种则是
等待队列(可有多个),通过Condition调用await()方法释放锁后,将加入等待队列。
在锁的实现类中会聚合同步器,然后利用同步器实现锁的语义:
-
锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节!
-
同步器面向的是锁的实现者——Daug Lea、自定义同步器,它简化了锁的实现方式,屏蔽了同步状态管理、线程排队、等待/唤醒等底层操作!
从AQS的类名称和修饰上来看,这是一个抽象类,所以从设计模式的角度来看,同步器一定是基于模版模式来设计的,使用者需要继承同步器,实现自定义同步器,并重写指定方法,随后将同步器组合在自定义的同步组件中,并调用同步器的模版方法,而这些模版方法又回调使用者重写的方法。
想理解上面这句话,只需要知道下面两个问题:
-
哪些是自定义同步器可重写的方法?
-
哪些是抽象同步器提供的模版方法?
同步器可重写的方法
自定义同步器实现的相关方法,只是为了通过修改state字段,来实现多线程的独占模式或者共享模式。自定义同步器需要实现以下方法(ReentrantLock需要实现的方法如下,并不是全部):
| 方法名 | 描述 |
|---|---|
| protected boolean isHeldExclusively() | 该线程是否正在独占资源。只有用到Condition才需要去实现它。 |
| protected boolean tryAcquire(int arg) | 独占方式。arg为获取锁的次数,尝试获取资源,成功则返回True。 |
| protected boolean tryRelease(int arg) | 独占方式。arg为释放锁的次数,尝试释放资源,成功则返回True。 |
| protected int tryAcquireShared(int arg) | 共享方式。arg为获取锁的次数,尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 |
| protected boolean tryReleaseShared(int arg) | 共享方式。arg为释放锁的次数,尝试释放资源,如果释放后允许唤醒后续等待结点返回True,否则返回False。 |
一般来说,自定义同步器要么是独占方式,要么是共享方式,它们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。AQS也支持自定义同步器同时,实现独占和共享两种方式,如ReentrantReadWriteLock。ReentrantLock是独占锁,所以实现了tryAcquire-tryRelease。
一般需要重写的方法也应该有abstract来修饰,自定义的同步组件或者锁不可能既是独占式又是共享式,为了避免强制重写不相干方法,所以就没有abstract来修饰了,但要抛出异常告知不能直接使用该方法:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
}
同步状态State方法
同步状态就是用volatile修饰的state——用于展示当前临界资源的获锁情况,所以在重写上面表格中的几个方法时,还要通过同步器提供的下面三个方法(AQS提供的)来获取或修改同步状态:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
/**
* The synchronization state.
*/
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
在
ReentrantLock内部维护了一个volatile修饰的变量state,通过CAS来进行读写(最底层还是交给硬件来保证原子性和可见性),如果CAS更改成功,即获取到锁,线程进入到try代码块继续执行;如果没有更改成功,线程会被【挂起】,不会向下执行。
compareAndSetState通常用于在获取到锁之前,尝试加锁时,对state进行修改。这种场景下,由于当前线程不是锁持有者,所以对state的修改是线程不安全的,也就是说可能存在多个线程都尝试修改state。所以需要保证对state修改的原子性操作,即使用了unsafe类的本地CAS方法;setState方法通常用于当前正持有锁的线程对state变量进行修改,不存在竞争,是线程安全的,所以此处没必要用CAS保证原子性,修改的性能更重要。
这几个方法都是final修饰的,说明子类中无法重写它们。
可以通过修改state字段表示的同步状态,来实现多线程的独占模式和共享模式(加锁过程):
对于自定义的同步工具,需要自定义获取同步状态和释放状态的方式,也就是AQS架构图中的第一层:API层。
ReentrantLock、ReentrantReadWriteLock、Semaphore(信号量)、CountDownLatch这几个类其实仅仅是在实现以上几个方法上略有差别,其他的实现都是通过同步器的模版方法来实现的。
同步器提供的模版方法
上面将同步器的实现方法分为独占式和共享式两类,模版方法其实除了提供以上两类模版方法之外,只是多了响应中断和超时限制的模版方法供Lock使用:
注意:上面的方法都有final关键字修饰,说明子类不能重写这个方法!
自定义互斥锁
package mylock;
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 MyMutex implements Lock {
/**
* 静态内部类-自定义同步器
*/
private static class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
// 调用AQS提供的方法,通过CAS保证原子性
// 通过CAS设置state状态,不管成功与否都会马上返回
if (compareAndSetState(0, arg)) {
/*
* 实现的是互斥锁,所以标记获取到同步状态(更新state成功)的线程,主要为了判断是否可重入
*/
setExclusiveOwnerThread(Thread.currentThread());
// 获取同步状态成功,返回true
return true;
}
// 获取同步状态失败,返回false
return false;
}
@Override
protected boolean tryRelease(int arg) {
// 未拥有锁,却让释放,会抛出IllegalMonitorStateException
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
// 可以释放,清空排它线程标记
setExclusiveOwnerThread(null);
// 设置同步状态为0,表示释放锁
setState(0);
return true;
}
/**
* 是否独占式持有
* @return true of false
*/
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
/**
* 主要用于等待/通知机制,每个condition都有一个与之对应的条件等待队列
* @return condition
*/
Condition newCondition() {
return new ConditionObject();
}
}
/**
* 聚合自定义同步器
*/
private final MySync sync = new MySync();
@Override
public void lock() {
// 阻塞式的获取锁,调用同步器模版方法独占式,获取同步状态
// 调用AbstractQueuedSynchronizer#tryAcquire(自定义)
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(1);
}
@Override
public Condition newCondition() {
// 使用自定义的条件
return sync.newCondition();
}
}
ReentrantLock、ReentrantReadWriteLock、Semaphore(信号量)、CountDownLatch都是按照这个结构实现的。
AQS架构图
-
上图中有颜色的为Method,无颜色的为Attribution。
-
AQS框架共分为五层,自上而下由浅入深,从AQS对外暴露的API,到底层基础数据。
-
当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行
加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。
下面会从整体到细节,从流程到方法逐一剖析AQS框架,主要分析过程如下:

AQS同步队列
AQS作为基础组件,对于锁的实现存在两种不同的模式,即共享模式(如Semaphore)和独占模式(如ReetrantLock)。无论是共享模式还是独占模式的实现类,其内部都是基于AQS实现的,也都维持着一个虚拟的同步队列。
当请求锁的线程超过现有模式的限制时,会将线程包装成Node结点,并将线程当前必要的信息存储到node结点中,然后加入同步队列等待获取锁。
这系列操作都有AQS协助完成,这也是作为基础组件的原因,无论是Semaphore还是ReetrantLock,其内部绝大多数方法都是间接调用AQS完成的。
因此,AQS核心思想可表述为
-
如果被请求的共享资源空闲,那么就将当前请求资源的线程,设置为有效的工作线程,将共享资源设置为
锁定状态; -
如果共享资源被占用,就需要一定的
阻塞等待唤醒机制,来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。
即,AQS使用一个volatile的int类型的成员变量,来表示同步状态(private volatile int state;),通过内置的FIFO队列,来完成资源获取的排队工作,通过CAS完成对State值的修改。
CLH(Craig、Landin and Hagersten)是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程,封装成一个节点,来实现锁的分配。
主要原理图如下:
队列中每个排队的个体就是一个Node!
Node节点及waitStatus
Node结点是对每一个访问同步代码的线程的封装,它包含了需要同步的线程本身以及线程的状态,如是否被阻塞,是否等待唤醒,是否已经被取消等。每个Node结点内部关联其前继结点prev和后继结点next,这样可以方便线程释放锁后快速唤醒下一个在等待的线程,Node是AQS的内部类。

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** 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;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
*/
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;
}
}
}
其中SHARED和EXCLUSIVE常量分别代表共享模式和独占模式:
-
所谓共享模式是一个锁允许多条线程同时操作,如信号量
Semaphore采用的就是基于AQS的共享模式实现的; -
而独占模式则是同一个时间段,只能有一个线程对共享资源进行操作,多余的请求线程需要排队等待,如
ReentranLock。
变量waitStatus则表示当前被封装成Node结点的等待状态,共有4种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE:
-
CANCELLED:值为1,在同步队列中等待的线程,等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。 -
SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。即,处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。 -
CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。 -
PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。 -
0状态:代表初始化状态。
pre和next,分别指向当前Node结点的前驱结点和后继结点;thread变量存储请求锁的线程。nextWaiter与Condition相关,代表等待队列中的后继结点。
同步队列的结构
AQS内部维护了一个同步队列,用于管理同步状态。
-
当线程获取同步状态失败时,就会将当前线程,以及等待状态等信息,构造成一个Node节点,将其
加入到同步队列中尾部,阻塞该线程; -
当同步状态被释放时,会
唤醒同步队列中首节点的线程获取同步状态。

head和tail分别是AQS中的变量,其中head指向同步队列的头部,注意head为空结点,不存储信息。而tail则是同步队列的队尾,同步队列采用的是双向链表的结构,这样可方便队列进行结点增删操作。
state变量则是代表同步状态
-
当线程调用
lock方法进行加锁后,如果此时state的值为0,则说明当前线程可以获取到锁(锁和同步状态代表同一个意思),同时将state设置为1,表示获取成功。 -
如果state已为1,也就是当前锁已被其他线程持有,那么当前执行线程将被封装为Node结点加入
同步队列等待。
非公平锁加锁与解锁过程
加锁过程

1.ReentrantLock#lock:
final void lock() {
// 执行CAS操作,获取同步状态
if (compareAndSetState(0, 1))
// 成功则将独占锁线程设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 否则再次请求同步状态
acquire(1);
}
-
首先用一个CAS操作,判断state是否是0(表示当前锁未被占用),如果是0则把它置为1,并且设置当前线程为该锁的独占线程,表示获取锁成功。当多个线程同时尝试占用同一个锁时(CAS操作保证设置state变量的原子性),CAS操作只能保证一个线程操作成功,剩下的只能去排队。
-
若通过CAS设置变量State(同步状态)失败,也就是获取锁失败,则进入
acquire方法进行后续处理。
2.AbstractQueuedSynchronizer#acquire:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
// 再次尝试获取同步状态
public final void acquire(int arg) {
// 调用自定义同步器重写的tryAcquire方法
// 如果获取锁失败,就会调用addWaiter加入到等待队列中去
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryAcquire(int arg) { // 点击tryAcquire,选择ReentrantLock#tryAcquire
throw new UnsupportedOperationException();
}
}
acquire方法干了三件事情:
-
tryAcquire(体现了不公平):会尝试再次通过CAS获取一次锁;非公平锁ReentrantLock#tryAcquire的流程是:- 检查state字段,若为0,表示此时锁未被占用(先前被占用的已经被释放了),那么尝试占用;
- 若不为0,检查当前锁是否被自己占用,若被自己占用,则更新state字段,表示重入锁的次数。
- 如果以上两点都没有成功,则获取锁失败,返回false。
-
addWaiter:将当前线程加入双向链表(同步队列)中; -
acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。
3.AbstractQueuedSynchronizer#addWaiter:
当执行acquire(1)时,会通过tryAcquire获取锁。即,会再次尝试非阻塞的获取同步状态,如果获取失败(tryAcquire返回false),则会调用addWaiter方法构造Node节点(由于ReentrantLock属于独占锁,因此结点类型为Node.EXCLUSIVE)用于封装线程及其相关信息,并加入同步队列尾部。
同步队列中tail是AQS的成员变量,指向队尾。如果即将加入的节点是第一个节点,则为tail肯定为空,那么将执行enq(node)操作,进行初始化。如果非第一个节点即tail指向不为null,直接尝试执行CAS操作加入队尾,如果CAS操作失败还是会执行enq(node)。enq(node)通过“死循环”确保节点被正确添加,最终将其设置为尾节点之后,才会返回。
4.acquireQueued挂起
addWaiter方法把对应的线程以Node的数据结构形式,加入到双端队列里,返回的是一个包含该线程的Node。而这个Node会作为参数,进入到acquireQueued方法中。acquireQueued方法可以对排队中的线程进行获锁操作。
即,添加到同步队列后,结点就会进入一个自旋过程,即每个结点都在观察时机,待条件满足,获取同步状态,然后从同步队列退出,并结束自旋,回到之前的acquire()方法。自旋过程是在acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法中执行的。
for(;;) {
// ...
if (p == head && tryAcquire(arg))
}
总的来说,一个线程获取锁失败了,被放入同步队列,acquireQueued会把放入队列中的线程不断去获取锁,直到获取成功或者不再需要获取(中断)被挂起!。
for(;;) {
//...
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
-
shouldParkAfterFailedAcquire()方法的作用是判断当前结点的前驱结点是否为SIGNAL状态(即等待唤醒状态)-
如果是则返回true。
-
如果结点的ws为
CANCELLED状态(值为1>0),即结束状态,则说明该前驱结点已没有用,应该从同步队列移除,执行while循环,直到寻找到非CANCELLED状态的结点。 -
倘若前驱结点的ws值不为
CANCELLED,也不为SIGNAL(当从Condition的条件等待队列转移到同步队列时,结点状态为CONDITION因此需要转换为SIGNAL),那么将其转换为SIGNAL状态,等待被唤醒。
-
-
parkAndCheckInterrupt:如果前驱节点的waitStatus是SIGNAL状态,即shouldParkAfterFailedAcquire方法会返回true ,同时又不是head节点,程序会继续向下执行parkAndCheckInterrupt方法,用于将当前线程挂起,称为WAITING状态,需要等待一个unpark()操作来唤醒它
解锁过程

代码实现独占式获取同步状态


lock(ReentrantLock)
// 自己实现
public class MyMutex implements Lock {
public void lock() {
// 阻塞式的获取锁,调用同步器模版方法独占式,获取同步状态
sync.acquire(1);
}
}
// ReentrantLock具体实现
public class ReentrantLock implements Lock, java.io.Serializable {
// 默认创建非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// #1
public void lock() {
sync.lock(); // 点击进入abstract void lock();
}
// #2
abstract void lock();
// #3 内部类非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 非公平锁加锁流程
*/
final void lock() {
// 执行CAS操作,获取同步状态
if (compareAndSetState(0, 1))
// 成功则将独占锁线程设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 否则再次请求同步状态
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
}
这块代码的含义为:
-
若通过CAS设置变量State(同步状态)成功,也就是获取锁成功,则将当前线程设置为独占线程。
if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread());-
首先用一个CAS操作,判断state是否是0(表示当前锁未被占用),如果是0则把它置为1,并且设置当前线程为该锁的独占线程,表示获取锁成功。当多个线程同时尝试占用同一个锁时(CAS操作保证设置state变量的原子性),CAS操作只能保证一个线程操作成功,剩下的只能去排队。
-
非公平即体现在这里,如果占用锁的线程刚释放锁,state置为0,而排队等待锁的线程还未唤醒时,新来的线程就直接抢占了该锁,那么就插队了。
-
-
若通过CAS设置变量State(同步状态)失败,也就是获取锁失败,则进入acquire方法进行后续处理。
第一步很好理解,但第二步获取锁失败后,后续的处理策略是怎么样的呢?
有以下两种可能:
-
将当前线程获锁结果设置为失败,获取锁流程结束。这种设计会极大降低系统的并发度,并不满足实际的需求。所以就需要下面这种流程,也就是AQS框架的处理流程。
-
AQS框架的处理流程:存在某种排队等候机制,线程继续等待,仍然保留获取锁的可能,获取锁流程仍在继续。
- 既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
- 处于排队等候机制中的线程,什么时候可以有机会获取锁呢?
- 如果处于排队等候机制中的线程一直无法获取锁,还是需要一直等待吗,还是有别的策略来解决这一问题?
acquire(AbstractQueuedSynchronizer)
acquire方法干了三件事情:
-
tryAcquire(体现了不公平):会尝试再次通过CAS获取一次锁;非公平锁ReentrantLock#tryAcquire的流程是:- 检查state字段,若为0,表示此时锁未被占用(先前被占用的已经被释放了),那么尝试占用;
- 若不为0,检查当前锁是否被自己占用,若被自己占用,则更新state字段,表示重入锁的次数。
- 如果以上两点都没有成功,则获取锁失败,返回false。
-
addWaiter:将当前线程加入上面锁的双向链表(同步队列)中; -
acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
// 再次尝试获取同步状态
public final void acquire(int arg) {
// 调用自定义同步器重写的tryAcquire方法
// 如果获取锁失败,就会调用addWaiter加入到同步队列中去
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryAcquire(int arg) { // 点击tryAcquire,选择ReentrantLock#tryAcquire
throw new UnsupportedOperationException();
}
}
这里传入参数arg表示要获取同步状态后设置的值(即要设置state的值),因为要获取锁,而status为0时是释放锁,1则是获取锁,所以这里一般传递参数为1。进入方法后首先会执行tryAcquire(arg)方法,该方法在AQS中并没有具体实现,而是交由子类实现。
tryAcquire(ReentrantLock):如何体现非公平
public class ReentrantLock implements Lock, java.io.Serializable {
static final class NonfairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 非公平即体现在这里,如果占用锁的线程【刚释放锁】,state置为0,
* 而【排队等待锁的线程还未唤醒】时,【新来的线程】就直接抢占了该锁,那么就【插队】了
*/
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取state变量值
int c = getState();
// 1.没有线程占用锁
if (c == 0) {
if (compareAndSetState(0, acquires)) { // 执行CAS操作
setExclusiveOwnerThread(current); // 占用锁成功,设置独占线程为当前线程
return true;
}
}
// 2.如果当前线程已获取锁,属于重入锁,再次获取锁后将status值加1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置当前同步状态,当前只有一个线程持有锁,
// 因为不会发生线程安全问题,可以直接执行setState(nextc)
setState(nextc);
return true;
}
// 3.获取锁失败
return false;
}
}
nonfairTryAcquire做了两件事:
-
一是尝试再次获取同步状态,如果没有线程占用锁,并获取成功,则将当前线程设置为OwnerThread;
-
二是判断当前线程current是否为OwnerThread,如果是则
属于重入锁,state自增1,并获取锁成功,返回true,反之失败,返回false,也就是tryAcquire(arg)执行失败,返回false。
需要注意的是nonfairTryAcquire(int acquires)内部使用的是CAS原子性操作设置state值,可以保证state的更改是线程安全的,因此只要任意一个线程调用nonfairTryAcquire(int acquires)方法并设置成功,即可获取锁,不管该线程是新到来的还是已在同步队列的线程,毕竟这是非公平锁,并不保证同步队列中的线程一定比新到来线程请求(可能是head结点刚释放同步状态,然后新到来的线程恰好获取到同步状态)先获取到锁。
公平锁与非公平锁tryAcquire方法
非公平锁与公平锁最大的区别:
-
公平锁在线程请求到来时先会判断同步队列是否存在节点,如果存在先执行同步队列中的节点线程,当前线程将封装成node加入
同步队列等待。 -
非公平锁:当线程请求到来时,不管同步队列是否存在线程结点,直接尝试获取同步状态,获取成功直接访问共享资源。在绝大多数情况下,非公平锁才是理想的选择,毕竟从
效率上来说,非公平锁总是胜于公平锁。

hasQueuedPredecessors是公平锁加锁时判断同步队列中是否存在有效节点的方法
-
如果返回False,说明当前线程可以争取共享资源;
-
如果返回True,说明队列中存在有效节点,当前线程必须加入到同步队列中。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
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());
}
}
当h != t时:
- 如果
(s = h.next) == null,同步队列正在有线程进行初始化,但只是进行到了Tail指向Head,没有将Head指向Tail,此时队列中有元素,需要返回True。 - 如果
(s = h.next) != null,说明此时队列中至少有一个有效节点。- 如果此时
s.thread == Thread.currentThread(),说明同步队列的第一个有效节点中的线程与当前线程相同,那么当前线程是可以获取资源的; - 如果
s.thread != Thread.currentThread(),说明同步队列的第一个有效节点线程与当前线程不同,当前线程必须加入进同步队列。
- 如果此时
该方法与nonfairTryAcquire(int acquires)方法唯一的不同是,在使用CAS设置尝试设置state值前,调用了hasQueuedPredecessors()判断同步队列是否存在结点,如果存在,必须先执行完同步队列中结点的线程**,当前线程进入等待状态。
addWaiter(enq)线程加入队列
当执行acquire(1)时,会通过tryAcquire获取锁。即,会再次尝试非阻塞的获取同步状态,如果获取失败(tryAcquire返回false),则会调用addWaiter方法构造Node节点(由于ReentrantLock属于独占锁,因此结点类型为Node.EXCLUSIVE)用于封装线程及其相关信息,并加入同步队列尾部。
同步队列中tail是AQS的成员变量,指向队尾。如果即将加入的节点是第一个节点,则为tail肯定为空,那么将执行enq(node)操作。如果非第一个节点即tail指向不为null,直接尝试执行CAS操作加入队尾,如果CAS操作失败还是会执行enq(node):
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
/**
* Creates and enqueues node for current thread and given mode.
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
// 将请求同步状态失败的【线程】,以及【节点模式(独占模式)】封装成【结点】
Node node = new Node(Thread.currentThread(), mode);
// 新建变量pred,将指针指向tail指向的节点
Node pred = tail;
/**
* 如果是【第一个结点】加入肯定为空,跳过。
* 如果【非第一个结点】则直接【执行compareAndSetTail入队操作】,尝试在【尾部】快速添加
*/
if (pred != null) {
node.prev = pred; // 【新加入的节点】的前驱节点,指向原来的尾节点
/*
* 因为如果【多个线程】同时获取同步状态【失败】,都会执行这段代码。
* 所以,通过CAS方式,确保安全的设置当前节点为最新的尾节点
*/
if (compareAndSetTail(pred, node)) {
pred.next = node; // 【曾经的尾节点】的后继节点指向当前节点
return node; // 返回新构建的节点
}
}
/*
* 如果Pred指针是Null(说明等待队列中没有元素),【或者】当前Pred指针和Tail指向的位置不同(说明被别的线程已经修改)
*
* 如果【第一次加入】或者【compareAndSetTail操作没有成功】执行enq入队操作
*/
enq(node);
return node;
}
/**
* CAS tail field. Used only by enq.
*/
private final boolean compareAndSetTail(Node expect, Node update) {
// 对tailOffset和Expect进行比较,
// 如果tailOffset的Node和Expect的Node地址是相同的,那么设置Tail的值为Update的值
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
/**
* 【第一次加入】初始化队列并且入队新节点,或者将【compareAndSetTail操作没有成功】的节点执行enq入队操作
*/
private Node enq(final Node node) {
// 通过“死循环”确保节点被正确添加,最终将其设置为尾节点之后才会返回
for (;;) {
Node t = tail;
// 如果尾节点为null,队列为空
if (t == null) { // Must initialize
// 构建一个哨兵节点,并将头部指针指向它
// 如果tail为空,则新建一个head节点,并且tail指向head
if (compareAndSetHead(new Node())) // 创建并使用CAS设置头结点
tail = head;
} else {
// 将新节点的前驱节点指向t
node.prev = t;
if (compareAndSetTail(t, node)) { // 将新节点加入到队列尾节点
t.next = node; // 前驱节点的后继节点指向当前新节点,完成双向队列
return t;
}
}
}
}
}
addWaiter#enq
该方法做了两件事:
-
一是,如果还没有初始同步队列,则创建新结点并使用compareAndSetHead设置头结点,tail也指向head;
-
二是队列已存在,则将新结点node添加到队尾。
这两个步骤都存在同一时间多个线程操作的可能,如果有一个线程修改head和tail成功,那么其他线程将继续循环,直到修改成功,这里使用CAS原子操作,进行头结点设置和尾结点tail替换可以保证线程安全。

从这里也可以看出head结点本身不存在任何数据,它只是作为一个哨兵节点,而tail永远指向尾部结点(前提是队列不为null)。

这里体现了经典的自旋+CAS组合来实现非阻塞的原子操作。由于compareAndSetHead的实现使用了unsafe类提供的CAS操作,所以只有一个线程会创建head节点成功。
假设线程A入队成功,之后B、C开始新一轮循环,此时tail已经不为空,两个线程都走到else里面。假设B线程compareAndSetTail成功,那么B就可以返回了,C由于入队失败,还需要一轮循环。最终所有线程都可以成功入队。当B、C入等待队列后(A不在队列里),此时AQS队列如下:
acquireQueued
addWaiter方法把对应的线程以Node的数据结构形式,加入到双端队列里,返回的是一个包含该线程的Node。而这个Node会作为参数,进入到acquireQueued方法中。acquireQueued方法可以对排队中的线程进行获锁操作。
即,添加到同步队列后,结点就会进入一个自旋过程,即每个结点都在观察时机,待条件满足,获取同步状态,然后从同步队列退出,并结束自旋,回到之前的acquire()方法。自旋过程是在acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法中执行的。
总的来说,一个线程获取锁失败了,被放入同步队列,acquireQueued会把放入队列中的线程不断去获取锁,直到获取成功或者不再需要获取(中断)被挂起!。
1.acquireQueued方法的流程
一个线程获取锁失败了,被放入等待队列,acquireQueued会把放入队列中的线程不断去获取锁,直到获取成功或者不再需要获取(中断)。
跳出当前循环的条件是当“前置节点是头结点,且当前线程获取锁成功”。为了防止因死循环导致CPU资源被浪费,我们会判断前置节点的状态来决定是否要将当前线程挂起,具体挂起流程用流程图表示如下(shouldParkAfterFailedAcquire流程):
2.acquireQueued源码
下面我们从“何时出队列?”和“如何出队列?”两个方向来分析一下acquireQueued源码:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
final boolean acquireQueued(final Node node, int arg) {
// 标记是否成功拿到资源(锁)
boolean failed = true;
try {
// 标记等待过程中是否中断过
boolean interrupted = false;
// "死循环",尝试获取锁,或者挂起
for (;;) {
// 获取【当前节点】的【前驱节点】
final Node p = node.predecessor();
// 只有【当前节点】的【前驱节点】是【头节点】,才会尝试获取锁(哨兵节点作用)
// 如果p是头结点,说明【当前节点】在真实数据队列的【首部】,就尝试获取锁(头结点是虚节点)
// 跳出当前循环的条件是,当“前置节点是头结点,且当前线程获取锁成功”
if (p == head && tryAcquire(arg)) {
// 注意这一步操作!
// 【获取同步状态成功,将获得锁的当前节点设置为head节点——虚节点】(头指针移动到当前node)
setHead(node);
p.next = null; // 原head节点出队(原哨兵节点的后继节点置为空),在某个时间点被GC回收
failed = false; // 获取成功
return interrupted; // 返回是否被中断过
}
/*
* 判断获取失败后是否可以挂起,若可以,则挂起
*
* 说明“p不为头结点”【或者】“当前节点没有获取到锁”(可能是由于非公平锁,被抢占了)
*【或者】是“p不是头节点”且“当前节点没有获取到锁”
* 这个时候就要判断当前node是否要被阻塞【被阻塞条件:前驱节点的waitStatus为-1:SIGNAL】,
* 防止无限循环浪费资源
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; // 线程若被中断,设置interrupted为true
}
} finally {
if (failed)
cancelAcquire(node); // 最终都没能获取同步状态,结束该线程的请求
}
}
}
当前线程在自旋(死循环)中获取同步状态,当且仅当,当前结点的前驱结点为头结点才尝试获取同步状态,这符合FIFO的规则,即先进先出。
其次node是当前获取同步状态的线程结点,只有当node释放同步状态,唤醒后继结点,后继结点才有可能获取到同步状态。后继结点,在其前继结点为head时,才进行尝试获取同步状态,其他时刻将被挂起。
注意:
-
当线程请求锁而等待后,将加入
同步队列等待(等待被唤醒); -
通过Condition调用
await()方法释放锁后,将加入等待队列。
3.setHead
进入第一个if语句后调用setHead(node)方法,将当前线程结点设置为head。setHead方法是把当前节点置为虚节点,但并没有修改waitStatus,因为它是一直需要用的数据。
node结点被设置为head后,其thread信息和前驱结点将被清空,因为该线程已获取到同步状态(锁),正在执行了,也就没有必要存储相关信息了,head只有保存指向后继结点的指针即可,便于head结点释放同步状态后,唤醒后继结点:

4.shouldParkAfterFailedAcquire
如果前驱结点不是head,或者,假设B和C在竞争锁的过程中,A一直持有锁,那么它们的tryAcquire操作都会失败,因此会走到第2个if语句中。shouldParkAfterFailedAcquire(p, node)和parkAndCheckInterrupt()会将获取同步状态失败的线程挂起:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
// 判断当前结点的前驱结点是否为SIGNAL状态(即【等待唤醒状态】)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前驱节点(头节点)的状态
int ws = pred.waitStatus;
// 如果是SIGNAL状态:-1,即等待被占用的资源释放(说明头结点处于唤醒状态),直接返回true
// 准备继续调用parkAndCheckInterrupt方法
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release to signal it, so it can safely park.
*/
return true;
// ws > 0说明前驱节点是CANCELLED状态:1
if (ws > 0) {
// 循环判断前驱节点的前驱节点是否也为CANCELLED状态,忽略该状态的节点,重新连接队列
do {
// 循环向前查找取消节点,把所有取消节点从队列中剔除
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 将【当前节点】的【前驱节点】设置为SIGNAL状态,用于后续唤醒操作
// 程序第一次执行到这返回为false,还会进行外层第二次循环
/*
* 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;
}
}
shouldParkAfterFailedAcquire()方法的作用是判断当前结点的前驱结点是否为SIGNAL状态(即等待唤醒状态)
-
如果是则返回true。
-
如果结点的ws为
CANCELLED状态(值为1>0),即结束状态,则说明该前驱结点已没有用,应该从同步队列移除,执行while循环,直到寻找到非CANCELLED状态的结点。 -
倘若前驱结点的ws值不为
CANCELLED,也不为SIGNAL(当从Condition的条件等待队列转移到同步队列时,结点状态为CONDITION因此需要转换为SIGNAL),那么将其转换为SIGNAL状态,等待被唤醒。
5.parkAndCheckInterrupt
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; // 线程若被中断,设置interrupted为true
只有shouldParkAfterFailedAcquire(p, node)是true,程序才会去继续执行parkAndCheckInterrupt方法。
所以,线程入队后能够挂起的前提是,它的前驱节点的状态为SIGNAL,它的含义是“Hi,前面的兄弟,如果你获取锁并且出队后,记得把我唤醒!”。
如果前驱节点的waitStatus是SIGNAL状态,即shouldParkAfterFailedAcquire方法会返回true ,同时又不是head节点,程序会继续向下执行parkAndCheckInterrupt方法,用于将当前线程挂起,称为WAITING状态,需要等待一个unpark()操作来唤醒它:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
// parkAndCheckInterrupt主要用于挂起当前线程,阻塞调用栈,返回当前线程的中断状态
private final boolean parkAndCheckInterrupt() {
// 将当前线程挂起
LockSupport.park(this);
/**
* 根据park方法API描述,程序在下述三种情况会继续向下执行
* 1.被unpark;2.被中断(interrupt);3.其他不合逻辑的返回才会继续向下执行
* 因上述三种情况程序执行至此,返回当前线程的中断状态,并清空中断状态
* 如果由于被中断(不再处于WAITING状态),该方法会返回true
*/
return Thread.interrupted();
}
}
被唤醒的程序会继续执行acquireQueued方法里的循环(for(;;)"死循环",尝试获取锁,或者挂起),如果获取同步状态成功,则会返回interrupted = true的结果。
public final void acquire(int arg) {
// 调用自定义同步器重写的tryAcquire方法
// 如果获取锁失败,就会调用addWaiter加入到同步队列中去
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
代码实现独占式释放同步状态
解锁的流程**:先尝试释放锁,
-
若释放成功,那么查看头结点的状态是否为SIGNAL,如果是则唤醒头结点的下个节点关联的线程;
-
如果释放失败那么返回false,表示解锁失败。
每次都只唤起头结点的下一个节点关联的线程。
unlock
ReentrantLock在解锁的时候,并不区分公平锁和非公平锁!
public class ReentrantLock implements Lock, java.io.Serializable {
public void unlock() {
sync.release(1);
}
}
AQS#release
调用AQS模版方法release:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final boolean release(int arg) {
// 如果返回true,说明该锁没有被任何线程持有
if (tryRelease(arg)) {
// 释放成功,获取头节点
Node h = head;
// 存在头节点,并且waitStatus不是初始状态
// 在获取的过程中会将waitStatus的值从初始状态更新成SIGNAL状态
if (h != null && h.waitStatus != 0)
// 解除线程挂起状态
unparkSuccessor(h); // 唤醒后继结点的线程
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
}
if (h != null && h.waitStatus != 0):
- h == null Head还没初始化:初始情况下,head == null,第一个节点入队,Head会被初始化一个虚拟节点。所以说,这里如果还没来得及入队,就会出现head == null的情况。
- h != null && waitStatus == 0:表明后继节点对应的线程仍在运行中,不需要唤醒。
- h != null && waitStatus < 0:表明后继节点可能被阻塞了,需要唤醒。
1.ReentrantLock#Sync#tryRelease
public class ReentrantLock implements Lock, java.io.Serializable {
/**
* 在ReentrantLock里面的公平锁和非公平锁的父类Sync定义了可重入锁的释放锁机制
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
// 释放当前线程占用的锁,返回是否释放成功
protected final boolean tryRelease(int releases) {
// 减少可重入次数(计算释放后state值)
int c = getState() - releases;
// 当前线程不是持有锁的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果持有线程全部释放,将当前独占锁所有线程设置为null,并更新state
if (c == 0) { // 锁被重入次数为0,表示释放成功
free = true;
setExclusiveOwnerThread(null); // 清空独占线程
}
setState(c); // 更新state值
return free;
}
}
}
tryRelease是ReentrantLock类中内部类自己实现的,因为AQS对于释放锁并没有提供具体实现,必须由子类自己实现,其过程为:
-
当前释放锁的线程若不持有锁,则抛出异常。
-
若持有锁,计算释放后的state值是否为0,若为0表示锁已经被成功释放,并且清空独占线程,最后更新state值,返回free。
2.release#unparkSuccessor
unparkSuccessor方法作用是,要唤醒头节点的后继节点:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
private void unparkSuccessor(Node node) {
// 获取头节点的waitStatus
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 清空头节点的waitStatus值,即置为0
Node s = node.next; // 获取当前节点的后继节点——下一个需要唤醒的结点
// 判断当前节点的后继节点是否是【取消状态】或者【null】,如果是,需要移除,重新连接队列
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾节点向前查找,找到队列第一个waitStatus状态小于0的节点
for (Node t = tail; t != null && t != node; t = t.prev)
// 如果是独占式,这里小于0,其实就是SIGNAL
if (t.waitStatus <= 0)
s = t;
}
// 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点unpark
if (s != null)
// 解除线程挂起状态
LockSupport.unpark(s.thread);
}
}
为什么这个地方是从队列尾部向前查找第一个非CANCELLED的节点?
- 原因一:由节点加入队列的情景可知:
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; // 1
if (compareAndSetTail(pred, node)) { // 2
pred.next = node; // 3
return node;
}
}
enq(node);
return node;
}
节点入队并不是原子操作:
node.prev = pred;
compareAndSetTail(pred, node)
这两个地方可以看作是尾节点入队的原子操作,如果此时代码还没执行到pred.next = node;(第1、2和3之间并非原子操作),这时又恰巧执行了unparkSuccessor方法,就没办法从前往后找了,因为后继指针还没有连接起来,所以需要从后往前找。
- 原因二:在产生CANCELLED状态节点的时候,先断开的是Next指针,Prev指针并未断开,因此也是必须要从后往前遍历才能够遍历完全部的Node。
综上所述,如果是从前往后找,由于极端情况下入队的非原子操作和CANCELLED节点产生过程中断开Next指针的操作,可能会导致无法遍历所有的节点。
独占式tryLock
非阻塞式的获取锁,尝试获取,获取不到不会阻塞,直接返回false:
public class ReentrantLock implements Lock, java.io.Serializable {
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
/**
* 非公平即体现在这里,如果占用锁的线程【刚释放锁】,state置为0,
* 而【排队等待锁的线程还未唤醒】时,【新来的线程】就直接抢占了该锁,那么就【插队】了
*/
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取state变量值
int c = getState();
// 1.没有线程占用锁
if (c == 0) {
if (compareAndSetState(0, acquires)) { // 执行CAS操作
setExclusiveOwnerThread(current); // 占用锁成功,设置独占线程为当前线程
return true;
}
}
// 2.如果当前线程已获取锁,属于重入锁,再次获取锁后将status值加1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置当前同步状态,当前只有一个线程持有锁,
// 因为不会发生线程安全问题,可以直接执行setState(nextc)
setState(nextc);
return true;
}
// 3.获取锁失败
return false;
}
}
nonfairTryAcquire工作流程:
- 检查state字段,若为0,表示此时锁
未被占用(先前被占用的已经被释放了),那么尝试占用; - 若不为0,检查当前锁
是否被自己占用,若被自己占用,则更新state字段,表示重入锁的次数。 - 如果以上两点都没有成功,则获取锁失败,返回false。
需要注意的是nonfairTryAcquire(int acquires)内部使用的是CAS原子性操作设置state值,可以保证state的更改是线程安全的,因此只要任意一个线程调用nonfairTryAcquire(int acquires)方法并设置成功,即可获取锁,不管该线程是新到来的还是已在同步队列的线程,毕竟这是非公平锁,并不保证同步队列中的线程一定比新到来线程请求(可能是head结点刚释放同步状态,然后新到来的线程恰好获取到同步状态)先获取到锁。
独占式tryLock(timeout, unit)
tryLock(long timeout, TimeUnit unit)提供了超时获取锁的功能。它的语义是在指定的时间内如果获取到锁就返回true,获取不到则返回false。这种机制避免了线程无限期的等待锁释放。
那么超时的功能是怎么实现的呢?
public class ReentrantLock implements Lock, java.io.Serializable {
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
// 调用同步器模版方法,可响应中断和超时时间限制
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
tryAcquireNanos
-
如果线程被中断了,那么直接抛出InterruptedException。
-
如果未中断:
-
先尝试获取锁,获取成功就直接返回
-
获取失败则进入
doAcquireNanos。
-
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
}
doAcquireNanos
-
线程先进入同步队列;
-
然后开始自旋,尝试获取锁
-
获取成功就返回;
-
失败则在队列里找一个安全点把自己挂起,直到超时时间过期。
-
这里为什么还需要循环呢?因为当前线程节点的前驱状态可能不是SIGNAL,那么在当前这一轮循环中线程不会被挂起,然后更新超时时间,开始新一轮的尝试。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
/**
* 在有限的时间内去竞争锁
* @return 是否获取成功
*/
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
// 计算超时截止时间
final long deadline = System.nanoTime() + nanosTimeout;
// 以独占方式加入到同步队列中
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
// 自旋
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// 计算新的超时时间
nanosTimeout = deadline - System.nanoTime();
// 1.如果超时,直接返回false
if (nanosTimeout <= 0L)
return false;
// 2.需要挂起,且超时时间未到
if (shouldParkAfterFailedAcquire(p, node) &&
// 判断是最新超时时间是否大于阈值1000
// static final long spinForTimeoutThreshold = 1000L;
nanosTimeout > spinForTimeoutThreshold)
// 挂起线程,时间到(nanosTimeout),自动返回
// 阻塞当前线程直到超时时间到期
LockSupport.parkNanos(this, nanosTimeout);
// 3.响应中断
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
}
独占式响应中断获取同步状态lockInterruptibly
public interface Lock {
void lockInterruptibly() throws InterruptedException;
}
public class ReentrantLock implements Lock, java.io.Serializable {
public void lockInterruptibly() throws InterruptedException {
// 调用同步器模版方法可中断式获取同步状态
sync.acquireInterruptibly(1);
}
}
AQS#acquireInterruptibly
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 尝试非阻塞式获取同步状态,如果没有获取到同步状态,执行doAcquireInterruptibly
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
}
doAcquireInterruptibly
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 获取中断信号后,不再返回interrupted = true的值,
// 而是直接抛出InterruptedException
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
}
参考资料
1.Java AQS队列同步器以及ReentrantLock的应用
2.从ReentrantLock的实现看AQS的原理及应用

浙公网安备 33010602011771号