关联文章:
Java并发源码分析之Semaphore
Java并发源码分析之ReentrantReadWriteLock
Java并发源码分析之Condition
Java并发源码分析之CyclicBarrier
Java并发源码分析之CountDownLatch
工作原理概要
AQS全称是AbstractQueuedSynchronizer,抽象同步队列,是用来构建锁或其他组件的基础框架,相关类图如下:
AbstractOwnableSynchronizer:抽象类,定义了独占锁线程,exclusiveOwnerThread
AbstractQueuedSynchronizer:抽象类,继承了AbstractOwnableSynchronizer,里面包含内部类Node
Lock:接口,定义了获取锁和释放锁的方法
ReentrantLock:类,实现了Lock,里面包含三个内部类Sync、NonfairSync、FairSync
Sync:类,继承了AbstractQueuedSynchronizer,重写了tryRelease方法
NonfairSync:类,继承了Sync,重写了tryAcquire
FairSync:类,继承了Sync,重写了tryAcquire
原理概述
在AbstractOwnableSynchronizer中定义了一个exclusiveOwnerThread变量,用来表示获取到排它锁的线程。
AQS中定义state,标识同步状态,0代表锁未被占用,1代表锁已被占用;在排它锁中表示可重入次数,在共享锁中表示剩余共享锁的个数。
AQS里面有两种队列,同步队列和等待队列,通过内部类Node实现的,等待队列是用来实现类似于Object的wait和notify的功能。同步队列是一个FIFO的双向链表,便于对里面的节点进行增删操作。head指向同步队列头部,是一个空节点,不存储信息,tail指向同步队列的队尾。
AQS中关键变量
// 同步队列的头节点(不包含线程信息)
private transient volatile Node head;
// 同步队列的尾节点
private transient volatile Node tail;
// 同步状态,0代表锁未被占用,1代表锁已被占用;在排它锁中表示可重入次数,在共享锁中表示剩余共享锁的个数
private volatile int state;
// 内部类,队列节点
static final class Node {
// 共享锁节点
static final Node SHARED = new Node();
// 排它锁节点
static final Node EXCLUSIVE = null;
/** 等待状态常量 */
// waitStatus的状态, 值小于0都可以认为是等待状态, 大于0则是无效状态, 需要将其从同步队列中处理掉
// 取消状态, 不想申请或释放锁了
static final int CANCELLED = 1;
// 等待通知状态, 当节点变为这个状态, 说明后继节点等待被唤醒
static final int SIGNAL = -1;
// 条件状态, await/signal场景下使用
static final int CONDITION = -2;
// 常用作共享锁模式, 表示当前节点SHARED后继节点能够得以执行
static final int PROPAGATE = -3;
// 等待状态
volatile int waitStatus;
// 前驱节点指针
volatile Node prev;
// 后继节点指针
volatile Node next;
// 当前线程
volatile Thread thread;
// 同步队列中用来标记节点类型,等待队列中用来指向后继节点
Node nextWaiter;
}
AQS中方法
抽象方法
AQS采用的是模板模式构建的,其内部除了提供并发操作核心方法以及同步队列操作外,还提供了一些模板方法让子类实现。
// 尝试获取排它锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 尝试释放排它锁
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// 尝试获取共享锁
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
// 尝试释放共享锁
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
主要入口方法
// 获取排它锁,不响应中断
acquire(int arg)
// 获取排它锁,响应中断
acquireInterruptibly(int arg)
// 带有超时时间获取排它锁,响应中断
tryAcquireNanos(int arg, long nanosTimeout)
// 释放排它锁
release(int arg)
// 获取共享锁,不响应中断
acquireShared(int arg)
// 获取共享锁,响应中断
acquireSharedInterruptibly(int arg)
// 带有超时时间获取共享锁,响应中断
tryAcquireSharedNanos(int arg, long nanosTimeout)
// 释放共享锁
releaseShared(int arg)
线程中断,当线程执行Thread.interrupt()方法时,会中断线程,这里的中断并不是线程停止执行,只是设置一个线程中断标识。响应中断,即获取到这个中断标识,然后进行处理,AQS是抛出InterruptedException异常。
由于代码都大致相同,这里只解析常用的lock和unlock
ReentrantLock获取锁流程
构造函数
// 默认为非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 传参判定使用非公平锁或公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁,当线程请求获取锁时,如果同步队列中存在等待的线程,则放入队列尾部进行排队等待。
非公平锁,直接请求获取锁。获取未成功,再将线程放入队列尾部进行排队等待。
1、ReentrantLock–获取锁入口
public void lock() {
sync.lock();
}
// 非公平锁实现
final void lock() {
if (compareAndSetState(0, 1))
// 获取成功,则设置独占锁线程变量为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// AQS中的方法
acquire(1);
}
// 公平锁实现
final void lock() {
// AQS中的方法
acquire(1);
}
非公平锁的实现中,在调用AQS中的acquire方法之前,先尝试通过CAS将state的值从0更新成1
2、AQS–acquire获取锁
// 获取排它锁
public final void acquire(int arg) {
// tryAcquire尝试获取锁,获取失败封装成独占节点加入同步队列;
// 加入队列失败,将线程中断,不再获取锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
3、ReentrantLock–tryAcquire尝试获取锁
// 非公平锁实现
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// state=0,说明此时没有线程获取锁,再次尝试获取锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// 获取成功,设置独占锁线程变量为当前线程,返回成功
setExclusiveOwnerThread(current);
return true;
}
}
// state !=0,判断是否是当前线程再次获取锁,可重入
else if (current == getExclusiveOwnerThread()) {
// 将state+1
int nextc = c + acquires;
// state<0,肯定是出现异常了,正常情况下state>=0
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置state,并返回成功
setState(nextc);
return true;
}
return false;
}
// 公平锁实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// state=0,说明此时没有线程获取锁,再次尝试获取锁
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;
}
尝试获取排它锁,非公平锁先判断同步状态是否为0,为0则通过CAS更新同步状态,更新成功即获取锁成功;如果不为0,则需要判断是否是同一线程再次获取锁,ReentrantLock是支持可重入的,就要用到了父类变量exclusiveOwnerThread,是同一线程则将state+1,未成功返回获取锁失败。
公平锁多了一步判断队列是否为空,其他的一致。
4、AQS–addWaiter将线程封装成独占节点并添加到队列尾部
// mode=Node.EXCLUSIVE,返回新生成的节点
private Node addWaiter(Node mode) {
// 将当前线程封装成Node节点
Node node = new Node(Thread.currentThread(), mode);
// node应该放置在队列尾部,即尾节点的后继节点,先获取尾节点pred
Node pred = tail;
// pred不为空,先尝试通过CAS操作将node放置到队列尾部
if (pred != null) {
node.prev = pred;
// compareAndSetTail中会将tail指针指向node
if (compareAndSetTail(pred, node)) {
// 设置成功,将尾节点的后继指针指向node
pred.next = node;
return node;
}
}
// 队列为空或者CAS操作失败
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 {
// 跟上面一致,CAS操作
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq方法多了一步初始化队列的操作,CAS操作保证每次只有一个节点可以添加到队列中,自旋保证所有节点都可以添加到队列中。
5、AQS–acquireQueued自旋等待,条件满足再次申请获取锁
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)
// failed正常是false,不会走到这里。如果中间出现异常,说明当前线程已经无法正常申请锁了,则取消申请
cancelAcquire(node);
}
}
// 设置头节点,会将节点内容清空
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
// pred:前驱节点,node:当前节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前驱节点的等待状态
int ws = pred.waitStatus;
// 等待状态为等待唤醒
if (ws == Node.SIGNAL)
return true;
// 无效状态,将node向前寻找,寻找到有效的节点并将node放置在其后
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 将等待状态设置为等待唤醒
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 挂起当前线程并且判断是否已中断
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
队列中的节点除了头节点的后继节点可以尝试获取锁之外,其他节点都会进行阻塞,同时其前驱节点的等待状态为等待唤醒。当有其他线程释放锁之后,队列中的节点依次出队进行尝试获取锁。
6、AQS–cancelAcquire取消申请锁
private void cancelAcquire(Node node) {
// 节点为空,直接返回
if (node == null)
return;
// 将节点线程设置为null
node.thread = null;
// 获取前驱节点
Node pred = node.prev;
// 前驱节点ws>0,说明前驱节点为无效节点,向前查找到最近的有效节点
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 获取前驱节点的后继节点
Node predNext = pred.next;
// 将当前节点的等待状态设置为取消状态
node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,将前驱节点设置从尾节点
if (node == tail && compareAndSetTail(node, pred)) {
// 将前驱节点的后继节点设置为null
compareAndSetNext(pred, predNext, null);
} else {
// 如果node不是尾节点,说明node有后继节点,移除node节点需要保证node的后继节点不受影响,因此需要根据情况觉得是否唤醒node后继节点
int ws;
// 如果node的后继节点需要唤醒信号,并且pred可以剔红,则将pred.next = node.next
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 通过CAS将node的后继节点设置为前驱节点的后继节点,即将node节点从链表里拿出来
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
// pred节点无法正常提供给后继节点唤醒信号,则直接唤醒node的后继节点
} else {
// 唤醒node的后继节点
unparkSuccessor(node);
}
// 将node节点的next指向自己,方便垃圾回收
node.next = node; // help GC
}
}
当node节点为同步队列中间节点时,需要判断pred节点是否可以提供node后继节点唤醒信号。
可以提供唤醒信号指的是,pred不为头节点 & (pred的等待状态为SIGNAL | pred的等待状态通过CAS成功修改为SIGNAL) & pred节点的线程不为null
可以提供唤醒信号,直接将node节点从链表中移除即可。无法提供唤醒信号,则需要直接唤醒node的后继节点,因为在其他线程释放锁时,无法再唤醒后继节点的线程了,只能在此时唤醒。
7、AQS–unparkSuccessor唤醒后继节点
// 唤醒node节点的后继节点
private void unparkSuccessor(Node node) {
// 获取等待状态
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 获取node节点的后继节点
Node s = node.next;
// 如果s为null, 或者waitStatus大于0(为CANCELLED, 取消状态)
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;
}
// s不为null, 唤醒s节点的线程
if (s != null)
LockSupport.unpark(s.thread);
}
ReentrantLock释放锁流程
1、ReentrantLock–unlock释放锁入口
public void unlock() {
sync.release(1);
}
2、AQS–release释放锁
public final boolean release(int arg) {
// 尝试释放锁
if (tryRelease(arg)) {
// 释放成功
Node h = head;
// 头节点不为空,并且头节点等待状态不为0
if (h != null && h.waitStatus != 0)
// 唤醒头节点的后继节点
unparkSuccessor(h);
return true;
}
return false;
}
3、ReentrantLock–tryRelease尝试释放锁
protected final boolean tryRelease(int releases) {
// 计算释放后的state的值
int c = getState() - releases;
// 独占锁线程变量不是当前线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果c-0,表示锁已经完全释放了
if (c == 0) {
// 释放成功
free = true;
// 独占锁线程变量设置为null
setExclusiveOwnerThread(null);
}
// 设置新的state,未完全释放即将state-1
setState(c);
return free;
}
当同步状态state>0时,说明未完全释放锁,该方法返回false,不唤醒队列中的节点
ReentrantLock–tryLock获取锁流程
public boolean tryLock() {
// 调用非公平锁的nonfairTryAcquire,与上述一致
return sync.nonfairTryAcquire(1);
}
tryLock只尝试一次获取锁,获取失败就返回失败,不会在同步队列中等待