简介
AQS全称AbstractQueueSynchronizer
,基于FIFO等待对列实现的一个同步器框架,JUC包中的很多类都基础自AQS,比如:ReentrantLock,ReentrantReadWriteLock、CountDownLatch等,AQS是在Java语言层面实现锁的机制,避免了用户态跟内核态之间的切换,我们知道Synchronized属于操作系统层面,在JDK5之前,使用synchronized对程序进行加锁,对应操作系统的MUTEX实现,在线程竞争激烈的环境下会造成频繁的上下文切换,进而严重影响并发性能,当然在JDK5 之后,官方对Synchronized做了很多的优化,比如偏向锁、轻量级锁、自适应锁,目前用Synchronized也是没有好大的性能问题了。
AQS核心是通过控制state
的值来判断是否成功获取锁,获取失败则将当前线程放入同步队列,
这里的头结点并没有设置thread,他仅仅持有state,以及head、tail
下面伪代码演示独占模式:
// state为0 说明没有线程获取到锁, 1 表示已经有线程持有锁,其他线程阻塞
volatile int state;
// 加锁
public void lock() {
// 使用CAS尝试获取锁
while (!compareAndSet(0, 1)) {
// 获取锁失败,进行自旋尝试
}
}
public void unlock() {
// 释放锁资源
while (!compareAndSet(1, 0)) {
// 自旋
}
}
public void test() {
lock();
// 处理业务逻辑
unlock();
}
源码解读
由于AQS只是提供的一个模板,对于锁如何实现独占,共享的具体方式将由自己进行重写,需要重写的方法有:
可以根据具体的需求选择性重写
- tryAcquire(): 独占式获取同步状态
- tryRelease(): 独占式释放同步转态
- tryAcquireShared(): 共享式获取同步状态 0 表示当前获取成功,随后的会获取失败,正数表示还有资源可用,负数表示获取失败
- tryReleaseShared(): 共享式释放同步状态
- isHeldExclusively(): 当前同步器是否在独占模式下被占用
Node 属性:
// 以共享模式存在的节点
static final Node SHARED = new Node();
// 以独享模式
static final Node EXCLUSIVE = null;
// 该节点线程在同步对列中等待超时或则被中断,将会从同步队列中取消等待,并移除该节点
static final int CANCELLED = 1;
// 标记后继节点的线程处于等待状态,用于通知后继节点,让后继节点运行,后继节点入队时将前驱结点设置为SIGNAL
static final int SIGNAL = -1;
// 节点处于等待队列中
static final int CONDITION = -2;
// 表示下一次共享式同步状态获取将会无条件地传播下去
static final int PROPAGATE = -3;
// 记录当前节点的状态,默认0表示初始状态
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后继节点
volatile Thread thread;
// 等待队列的后继节点
Node nextWaiter;
需要关注的核心方法:
独占模式
-
acquire: 获取同步状态,实际就是将state修改为arg
public final void acquire(int arg) { // tryAcquire: 尝试获取锁,该方法由具体的实现类进行重写,AQS中并没有实现该方法 // 如果尝试获取锁失败,那么将当前节点加入到同步队列的尾部, 在调用acquireQueued进行获取锁,或则阻塞线程 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 中断当前线程,acquireQueued返回true,说明有其他线程中断了当前线程而导致线程唤醒,并非调用的unpark唤醒,因为unpark是不会改变中断状态的 selfInterrupt(); }
-
addWaiter: 将当前线程节点添加到队列尾
private Node addWaiter(Node mode) { // 为当前线程新建一个Node节点 Node node = new Node(Thread.currentThread(), mode); // 通过CAS将node节点添加到同步队列的尾部 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) { // 队列为空进行初始化 if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
-
acquireQueued: 节点入队成功后,判断是否应该轮到当前节点运行
final boolean acquireQueued(final Node node, int arg) { // 获取锁是否失败 boolean failed = true; try { // 是否被其他线程异常中断,非unpark boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 前驱结点是head就尝试获取锁 if (p == head && tryAcquire(arg)) { // 获取锁成功,将当前节点设置为head setHead(node); p.next = null; // 清空的head节点,便于GC failed = false; return interrupted; } // 如果前驱节点不是head,对当前节点进行park // parkAndCheckInterrupt: 是否被其他线程异常中断,非unpark if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
-
shouldParkAfterFailedAcquire: 用于判断入队的节点是否能够安全地阻塞起来,入队时都要将
前驱节点设置为SIGNAL
-
会不会出现同步队列只有一个线程,即pred = null
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; // 前一个节点已经是SIGNAL说明可以安全的阻塞node if (ws == Node.SIGNAL) return true; // 如果前驱节点有被取消的,那么将其从同步队列中移除 if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // waitStatus必须为 0 或 PROPAGATE,将当前节点的前驱设置为SIGNAL状态 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
-
parkAndCheckInterrupt: 阻塞当前线程并检查中断信息
private final boolean parkAndCheckInterrupt() { // 进行阻塞线程 LockSupport.park(this); // 返回中断信息,如果线程被意外中断,这里将会返回true,正常情况下应该是使用unpark唤醒,返回false return Thread.interrupted(); } // 这里的blocker指当前对象 public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); // 调用OS 来阻塞线程,具体实现由OS实现 UNSAFE.park(false, 0L); setBlocker(t, null); }
-
concelAcquire: 如果当前线程获取锁过程发生了异常,取消进行尝试, 这个方法只有在抛出异常时会执行,比如在ReentrantLock中,调用tryAcquire后进入nonfairTryAcquire方法中重入数量过大抛出一个异常
private void cancelAcquire(Node node) {
// 如果当前节点为null,直接忽略
if (node == null)
return;
node.thread = null;
// 跳过被取消的节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 记录preNext,方便下面使用CAS修改
Node predNext = pred.next;
// 取消当前节点的
node.waitStatus = Node.CANCELLED;
// 如果node是尾结点,那么将pred设置为tail,同时将pre的next设置为null
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// 如果node不是尾结点,也不是头节点
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
// 如果,node的next不为null且转态小于等于0, 将pre的next设置为node的next
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 如果node节点是head, 唤醒node后续节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
private void unparkSuccessor(Node node) {
// 将node的状态设置为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 寻找node后面一个非concelled的节点来唤醒
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从tail开始遍历查找离node最近的非concelled节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒s节点的线程
LockSupport.unpark(s.thread);
}
-
release: 释放锁
public final boolean release(int arg) { // tryRelease: aqs没有具体实现 if (tryRelease(arg)) { Node h = head; // 如果头节点不为空,且状态不为0, 则唤醒h的后续节点 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
共享模式
方法跟独占模式差不多,独占模式获取锁失败后,入队,然后会继续竞争锁,而共享模式入队后将不会继续竞争锁
public final void acquireShared(int arg) {
// 获取锁失败,入队,
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
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);
}
}
ReentrantLock
- 实现Lock接口,Lock接口只是定义了一些加锁,解锁的一些抽象方法
- 有一个静态的抽象类Sync,继承AQS,
- 静态内部类FairSync、NoFairSync 继承Sync , 默认为非公平锁
使用比较简单,如下面一样,作为一名有追求的程序员,肯定不能局限于表面,继续研究底层源码
public void testLock() {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 处理业务逻辑
} finally {
lock.unlock();
}
}
ReentrantLock 顾名思义,可重入锁,支持公平,非公平锁
, 而Synchronized只支持非公平锁,通常来说非公平锁的性能要高于公平锁,因为在公平锁下,之前的线程运行时间很长而导致后来的线程始终得不到执行,非公平锁类似于抢占式,不管同步队列是否有线程,先尝试获取锁看下,如果获取成功就直接执行,没有获取成功就放入同步队列中
Sync
使用AQS的state来代表持有锁的数量
// 非公平获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 没有其他线程持有锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 当前获取锁的线程已经持有锁,进行重入
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 tryRelease(int releases) {
// 释放锁资源
int c = getState() - releases;
// 非持有锁的线程调用了tryRelease,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 锁是否被线程持有
boolean free = false;
if (c == 0) {
// 没有线程持有锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// 锁是否被当前线程独占
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
非公平锁
ReentrantLock默认为非公平
static final class NonfairSync extends Sync {
final void lock() {
// 首先使用CAS尝试修改状态,成功则设置当前线程为独占
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 修改失败,进行重试,重试失败,入队,继续重试,这里调用父类AQS的方法
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
公平锁
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors: 当前节点是否需要排队
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;
}
}
//当前节点是否需要排队 条件有点多,参考https://blog.csdn.net/weixin_38106322/article/details/107154961
public final boolean hasQueuedPredecessors() {
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());
}
大致流程如下: