AQS被称为java并发编程的基石,AQS使用一个用volatile
修饰的int
型的state
变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。通过 CLH 队列 和 状态变量 的协作,将复杂的线程调度抽象为简单使用CAS对的 state
操作。
ReentranLock
:可重入锁。可以公平获取锁和非公平获取锁。
AQS源码
AQS的整体结构
java
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
protected AbstractQueuedSynchronizer() { }
// 等待队列头部
private transient volatile Node head;
// 等待队列的尾部。初始化后,仅通过 casTail 修改。
private transient volatile Node tail;
// 同步状态
private volatile int state;
static final int WAITING = 1; // must be 1
static final int CANCELLED = 0x80000000; // must be negative
static final int COND = 2; // in a condition wait
/** CLH Nodes */
abstract static class Node {
volatile Node prev; // 头节点
volatile Node next; // 尾节点
Thread waiter; // 线程
volatile int status; // 状态
//...
}
// Concrete classes tagged by type
static final class ExclusiveNode extends Node { }
static final class SharedNode extends Node { }
static final class ConditionNode extends Node
implements ForkJoinPool.ManagedBlocker {
// ...
}
public class ConditionObject implements Condition, java.io.Serializable {
private transient ConditionNode firstWaiter;
private transient ConditionNode lastWaiter;
public ConditionObject() {
}
}
}
入队操作
java
final void enqueue(Node node) {
if (node != null) { // 确保节点非空
for (; ; ) { // 自旋直到成功
Node t = tail; // 获取当前尾节点
node.setPrevRelaxed(t); // 临时设置节点的前驱为当前尾节点
if (t == null) // 如果尾节点为null(队列为空),则尝试初始化头节点
tryInitializeHead();
else if (casTail(t, node)) { // CAS操作:尝试将尾节点替换为新节点
t.next = node; // CAS成功后,将原尾节点的next指向新节点
if (t.status < 0) // 如果原尾节点的状态小于0
LockSupport.unpark(node.waiter); // 唤醒新节点关联的线程
break;
}
}
}
}
// 如果在从尾部遍历中找到节点,则返回真
final boolean isEnqueued(Node node) {
for (Node t = tail; t != null; t = t.prev)
if (t == node)
return true;
return false;
}
唤醒后继节点
java
private static void signalNext(Node h) {
Node s;
if (h != null && (s = h.next) != null && s.status != 0) {
s.getAndUnsetStatus(WAITING); // 取消等待WAITING状态
LockSupport.unpark(s.waiter); // 唤醒线程
}
}
//
如果处于共享模式,则唤醒给定的节点
private static void signalNextIfShared(Node h) {
Node s;
if (h != null && (s = h.next) != null &&
(s instanceof SharedNode) && s.status != 0) {
s.getAndUnsetStatus(WAITING);
LockSupport.unpark(s.waiter);
}
}
清理队列和取消获取
java
private void cleanQueue() {
for (; ; ) {
for (Node q = tail, s = null, p, n; ; ) { // 三元组遍历:当前(q),前驱(p),后继(s)
if (q == null || (p = q.prev) == null)
return; // 队列为空或到达头部,终止清理
// 快照一致性检查,若后继节点为空且当前不是尾节点或后继不为空,后继节点的
// 前驱节点不是当前节点q或后继节点已经被取消,说明当前链已经被改变
if (s == null ? tail != q : (s.prev != q || s.status < 0))
break; // // 快照过期,中断当前遍历
// 发现已取消节点
if (q.status < 0) {
if ((s == null ? casTail(q, p) : s.casPrev(q, p)) &&
q.prev == p) {
p.casNext(q, s); // 更新前驱的后向链接
if (p.prev == null) // 如果前驱成为新头节点
signalNext(p); // 唤醒后继节点
}
break; // 本次清理完成,跳出内层循环
}
// 链接修复机制
if ((n = p.next) != q) { // 检测到断链
if (n != null && q.prev == p) {
p.casNext(n, q); // 修复前驱的后向链接
if (p.prev == null) // 如果前驱是头节点
signalNext(p); // 唤醒后继
}
break; // 修复完成,重新开始
}
// 前移指针
s = q; // 后继指针前移
q = q.prev; // 当前指针前移
}
}
}
// 取消正在进行的尝试获取锁的节点。
private int cancelAcquire(Node node, boolean interrupted,
boolean interruptible) {
if (node != null) {
node.waiter = null; // 解除对线程的引用,帮助GC
node.status = CANCELLED; // 标记节点为取消状态
// 如果节点已链接到队列中
if (node.prev != null)
cleanQueue(); // 触发队列清理机制
}
// 2. 中断状态处理
if (interrupted) {
if (interruptible) // 允许中断:直接返回取消状态
return CANCELLED;
else
Thread.currentThread().interrupt(); // 不允许中断:恢复线程中断状态
}
return 0;
}
ReentranLock
源码
我们先来看一下ReentranLock的整体结构和构造函数
整体结构:
java
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
}
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
}
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
}
}
构造函数:
java
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
从上面我们看到,ReentranLock里面使用了继承了AQS的同步器。实现了公平构造器和非公平同步器。默认使用非公平同步器,使用时构造方法是传入的fair
参数值为true
使用公平同步器。
ReentranLock的加锁过程。
java
public void lock() {
// 调用同步器的lock()方法
sync.lock();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final void lock() {
if (!initialTryLock())
acquire(1);
}
}
到目前为止NonfairSync
和fairSync
过程都是相同的。接下来就是两者的不同之处了
java
// fairSync的initialTryLock()方法
final boolean initialTryLock() {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取当前同步状态
int c = getState();
// 如果当前没有线程获取锁
if (c == 0) {
// 查询是否有任何线程正在排队等待acquire
// CAS将state的值设为1,表示获取到锁
// 将获取到锁的线程设为当前线程
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
// 如果当前线程之前已经获取到锁
} else if (getExclusiveOwnerThread() == current) {
if (++c < 0) // 判断重入次数是否达到最大值
throw new Error("Maximum lock count exceeded");
// 没有达到最大值,增加重入次数
setState(c);
return true;
}
return false;
}
// NonfairSync的initialTryLock()方法
final boolean initialTryLock() {
// 获取当前线程
Thread current = Thread.currentThread();
// CAS将state值设为1
if (compareAndSetState(0, 1)) {
将获取到锁的线程设为当前线程
setExclusiveOwnerThread(current);
return true;
} else if (getExclusiveOwnerThread() == current) { // 如果当前线程之前已经获取到锁
// 增加重入次数
int c = getState() + 1;
// 判断重入次数是否达到最大值
if (c < 0)
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
} else
return false;
}
根据源码我们可以看到,公平获取锁和非公平获取的区别就是:
公平获取锁还需要判断AQS的CLH队列是否有线程正在等待获取锁,有则无法获取到锁,加入队列
非公平获取锁只要能够将state的值从0修改为1成功就可以获取到锁
注:非公平锁指的是当前线程A刚执行完,这时新来一个线程B要获取锁,那么这个线程B就极可能更快地获取锁。非公平指的是未入队的线程相比已经入队的线程更先获取到锁。一旦线程入队,那么都是按照先来先服务进行获取锁,入对的线程都是公平的。并不是当前锁空闲时随机从同步队列中获取一个线程进行加锁。
非公平锁相比于公平锁效率更高,有以下几个原因:
- 从获取锁的过程来看,公平锁还需要判断同步队列是否有线程在等待,而非公平锁无需判断
- 公平锁还需要等待队列中的线程被唤醒然后获取锁;非公平无需等待,有活跃的线程到来直接获取锁
- 公共锁获取锁时还要检查是否是第一个节点线程,不是要需要切换
非公平获取锁可能存在线程饿死的情况,不断有新来的未入队的线程获取锁,会造成在队列中的线程一直无法获取到锁的情况。
initialTryLock()
可以理解为第一次尝试获取锁,即使未获取到锁也不会进行入队操作。入队操作还在后面
在initialTryLock()
中没有获取到会进入AQS的acquire()
方法,
java
public final void acquire(int arg) {
if (!tryAcquire(arg))
acquire(null, arg, false, false, false, 0L);
}
AQS的tryAcquire(int arg)
方法如下,被ReentranLock方法的同步器进行重写,实际调用的是ReentranLock中同步器重写的方法
java
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
tryAcquire(int arg)
重写方法如下
java
// fairSync的此方法
protected final boolean tryAcquire(int acquires) {
// 1、锁未被占用
// 2、当前线程是队列首节点
// 3、CAS更新锁状态
if (getState() == 0 && !hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 将当前线程设置为持有锁的线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// NonfairSync的此方法
protected final boolean tryAcquire(int acquires) {
// 锁未被占用,CAS设置当前状态
if (getState() == 0 && compareAndSetState(0, acquires)) {
// 将当前线程设置为持有锁的线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
如果此时还是没有获取到锁,就会进入AQS
的acquire()
方法进行再次尝试和入队。
java
final int acquire(Node node, int arg, boolean shared,
boolean interruptible, boolean timed, long time) {
Thread current = Thread.currentThread();
byte spins = 0, postSpins = 0;
boolean interrupted = false, first = false;
Node pred = null;
for (;;) {
// node不是首节点,前驱节点不为null,前驱节点不是head
if (!first && (pred = (node == null) ? null : node.prev) != null &&
!(first = (head == pred))) {
// 判断前驱节点状态是否处于cancel状态
if (pred.status < 0) {
// 清理队列
cleanQueue();
continue;
} else if (pred.prev == null) { // 前驱节点为null,自旋
Thread.onSpinWait();
continue;
}
}
if (first || pred == null) {
boolean acquired;
// 此时还未入队,再次尝试获取锁,若获取到了锁直接返回
try {
// 是否共享获取锁
if (shared)
acquired = (tryAcquireShared(arg) >= 0);
else
acquired = tryAcquire(arg);
} catch (Throwable ex) {
// 取消获取锁
cancelAcquire(node, interrupted, false);
throw ex;
}
// 获取成功,释放节点
if (acquired) {
if (first) { // 若为true,则此时肯定已入队
node.prev = null;
head = node;
pred.next = null;
node.waiter = null;
// 如果是共享获取锁,唤醒后续节点
if (shared)
signalNextIfShared(node);
if (interrupted)
current.interrupt();
}
return 1;
}
}
// 开始入队
Node t;
// 如果尾节点为null,进行初始化队列
if ((t = tail) == null) {
if (tryInitializeHead() == null) // 若初始化失败,则直接间隔尝试获取锁
return acquireOnOOME(shared, arg); // jdk17并没有这一步,后面才加入的,初始化失败会一直尝试初始化
} else if (node == null) { // 初始化此线程对应的节点
try {
node = (shared) ? new SharedNode() : new ExclusiveNode();
} catch (OutOfMemoryError oome) {
return acquireOnOOME(shared, arg);
}
} else if (pred == null) { // 尝试入队
node.waiter = current; // 将该节点的所对应的线程设为当前线程
node.setPrevRelaxed(t); // 将尾节点设为当前节点的前驱节点
if (!casTail(t, node)) // 将此节点设为尾节点
// 若设置失败,将前驱节点置为null。
node.setPrevRelaxed(null); // 设置失败说明可能有别的线程设置自身为尾节点,此节点要入队位置的前驱节点不再是之前的尾节点了
else
t.next = node;
} else if (first && spins != 0) {
--spins; // 减少自旋次数
Thread.onSpinWait();
} else if (node.status == 0) {
node.status = WAITING; // 自旋次数用尽后更新状态
} else {
long nanos;
spins = postSpins = (byte)((postSpins << 1) | 1); // 重至自旋次数
if (!timed) // 非超时模式
LockSupport.park(this); // 挂起线程,无限期阻塞
else if ((nanos = time - System.nanoTime()) > 0L) // 计算剩余时间
LockSupport.parkNanos(this, nanos); //限时阻塞
else // 已超时,跳出循环
break;
// 唤醒后状态管理,清除等待状态标记
node.clearStatus();
// 中断响应
if ((interrupted |= Thread.interrupted()) && interruptible)
break; // 响应中断退出
}
}
return cancelAcquire(node, interrupted, interruptible);
}
根据前面的源码我们知道传入的参数为acquire(null, arg, false, false, false, 0L);
该方法的执行过程如下:
- 再次尝试获取锁,此后每次循环只要还没有入队都会尝试获取锁
- 获取锁失败,判断是否需要初始化队列
- 创建节点
- 设置前驱节点,入队
- 入队之后查看自身是否是头节点,不是则一直自旋
- 成为首节点之后尝试获取锁,获取不到更新status状态
- 再次重试,还是获取不到锁进行无限期挂起(对于设置了超时时间的,未超时一直重试,直到超时,取消获取锁)
以上就是ReentrantLock的加锁全过程了。
ReentrantLock解锁
公平同步器和非公平同步器释放锁的过程是一样的
java
public void unlock() {
sync.release(1);
}
调用Sync同步器的release()解锁
java
public final boolean release(int arg) {
// 尝试释放资源
if (tryRelease(arg)) {
// 唤醒队列的下一个节点
signalNext(head);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// 获取释放后的状态
int c = getState() - releases;
// 检查持有锁的线程是否是当前线程
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException();
// 若释放之后同步状态为0
boolean free = (c == 0);
if (free)
setExclusiveOwnerThread(null); // 将记录当前持有锁的线程字段置null
setState(c); // 更新同步状态
return free;
}
以上就是ReentrantLock与AbstractQueuedSynchronizer加锁过程的核心源码解析。