AQS(AbstractQueuedSynchronizer),抽象的队列式的同步器框架,他提供了一个FIFO(first in first out)的队列,以基类的形式对外提供封装好的方法管理同步状态。
- 在AQS内部维护了一个叫做status的volatile变量,使得此变量具有内存可见性。可以通过以下几个方法操作:
- getState
- setState
- compareAndSetState
- AQS提供了两种形式的同步器
- 独占模式,基于此类的常见同步器如:ReentrantLock。
- 共享模式,基于此类的常见同步器如:CountDownLatch。
- 使用同步器时需要继承AbstractQueuedSynchronizer类,因为AQS只是一个同步器的框架,至于具体如何获取资源/释放资源,需要我们自己定义重写方法。
- isHeldExclusively(),查看是否处于占用状态
- tryAcquire(int acquires),独占方式,独占式获取同步状态,试着获取,成功返回true,反之为false。
- tryRelease(int releases),独占方式,独占式释放同步状态,等待中的其他线程此时将有机会获取到同步状态。
- tryReleaseShared(int arg),共享方式,共享式获取同步状态,返回值大于等于0,代表获取成功;反之获取失败。
- tryAcquireShared(int arg),共享方式,共享式释放同步状态,成功为true,失败为false。
- 这时候可能有人会问,既然必须自己实现为什么不设置为abstract方法? 原因是AQS框架不只提供了一种同步方式,还有上面提到的共享方式,如果设置为abstract方法,那么在使用中子类就必须将两种方式的资源获取/释放方法同时实现,这是不合理的,所以作者并未设置abstract。
基于AQS的独占锁实现代码
import java.util.concurrent.locks.ReentrantLock;
public class TestReentrantLock {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
try {
lock.lock();
System.out.println(1);
Thread.sleep(1000 * 60 * 3);
System.out.println(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
System.out.println(3);
lock.lock();
System.out.println(4);
} finally {
lock.unlock();
}
}).start();
}
}
- ReentrantLock的构造方法
// ReentrantLock的默认构造方法,初始化了非公平的同步器,即ReentrantLock默认是非公平实现
public ReentrantLock() {
sync = new NonfairSync();
}
// 可以指定ReentrantLock实现方式,FairSync公平的同步器,NonfairSync非公平的同步器
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
- 对比公平锁和非公平锁:公平性锁保证了锁的获取按照FIFO原则,而代价是线程的切换。非公平性锁虽然可能造成线程“饥饿”,但减少了线程切换,具有更大的吞吐量。
- 上面提到的线程切换的开销,就是非公平锁效率高于公平锁的原因,因为非公平锁减少了线程挂起的几率,比如unlock后队列中的下一个线程还没有被唤醒,或者刚醒但还没有抢占到锁,这时一个新的线程插队先一步获取了锁,后来的线程正好在线程切换的空档中继续执行了,所以非公平会效率更高。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 如果设置同步状态status成功,则设置拥有锁的线程为当前线程,获取锁成功
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//执行AQS入队操作,具体入队操作见下文
acquire(1);
}
// 实现AQS tryAcquire 方法,提供非公平的获取锁方式,AQS只提供了抽象的tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// status==0 && 设置同步状态成功,获取锁成功
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 锁重入
// 如果status != 0 && 当前获取锁的线程与正在执行的线程是同一个,也可以获取锁
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;
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
// 实现AQS tryAcquire 方法,提供公平的获取锁方式, AQS只提供了抽象的tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平与非公平实现的不同处,判断当前node是否在队列中,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;
}
}
// 查询是否有任何线程等待获取的时间长于当前线程
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
AQS框架实现代码
// tryAcquire 是FairSync和NonfairSync重写的
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 首先,调用使用者重写的tryAcquire方法,如果成功,后面的入队逻辑不再执行;若失败,进入步骤2
- 此时,获取同步状态失败,构造独占式同步结点,通过addWatiter将此结点添加到同步队列的尾部
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;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
- 因为设置同步状态失败了,所以创建节点并加入到AQS队列尾部
- 代码中使用compareAndSetTail()方法,采用CAS方式保证了线程的安全性,因为同一时间可能有多个线程正在尝试加入到队列尾部
- 如果队列没有初始化则执行enq
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- enq()同样使用CAS方式compareAndSetTail(),注意enq方法采用了自旋方式是一个死循环,保证了node一定会被设置成功,这是一种乐观并发策略
- 队列为空初始化队,并将node放到tail之后,注意这里返回的tail,并不是最后一个node
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)
cancelAcquire(node);
}
}
- 入队成功后,自旋检查node是不是老二节点,如果是则可以尝试去获取锁了,如果tryAcquire成功,原来的头节点会被踢出队列(p.next = null,被GC回收出队了),设置node为了head节点
- 如果当前node还不是老二节点,那么就要去检查自己前节点的状态是否正常,确保前节点可以正确执行,执行后可以唤醒自己,如果有存在问题的node,那么通过shouldParkAfterFailedAcquire调整队列中存在问题的node,调整后一切准备就绪后,执行parkAndCheckInterrupt()阻塞当前执行的线程等待唤醒
- cancelAcquire()只有出现异常时才会执行
- 注意此处同样采用自旋方式,即使线程被唤醒也会从上述第一步开始重新执行,直到自己tryAcquire成功便成AQS的头节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* -1 SIGNAL 表示后继线程需要被唤醒
* 意味着当前结点可以被安全地park
*/
return true;
if (ws > 0) {
/*
* 1 CANCELLED 表示线程已被取消(等待超时或者被中断),
* 只有CANCEL状态ws才大于0。若前驱结点处于CANCEL状态,也就是此结点线程已经无效,从后往前遍历,找到一个非CANCEL状态的结点,将自己设置为它的后继结点
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* -2 CONDITION 表示结点线程等待在condition上,当被signal后,会从等待队列转移到同步到队列中
* -3 PROPAGATE 表示下一次共享式同步状态会被无条件地传播下去
* 若前驱结点为其他状态,将其设置为SIGNAL状态
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- node默认waitStatus=0
- shouldParkAfterFailedAcquire是在死循环中调用,第一次进入waitStatus=0会执行else将waitStatus CAS赋值为 Node.SIGNAL状态,会一直遍历到ws == Node.SIGNAL为止
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 队列中所有节点waitStatus都调整完毕后,阻塞当前线程,当锁被释放后被唤醒的线程从 LockSupport.park(this); 继续执行下一次循环,尝试获取锁 执行 tryAcquire,步骤参考前几步
// 出现异常当前node应被提出队列
private void cancelAcquire(Node node) {
// 忽略不存在的节点
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
// 注意我们之前提到过waitStatus > 0只有一种状态,即CANCELLED状态,表示线程已被取消(等待超时或者被中断)
// 注意这里是while循环
while (pred.waitStatus > 0){
//拆分成几步方便理解
//pred = node.prev.prev;
//node.prev = pred;
//意思是跳过node之前所有的CANCELLED状态节点,找到一个状态正确的节点来做node的前节点
node.prev = pred = pred.prev;
}
// 此时的pred对象肯定是一个状态不为CANCELLED的节点
// predNext对象就是队列中有状态不正常的第一个节点
// pred对象就是队列中node所在位置之前第一个正常的节点
Node predNext = pred.next;
// 设置CANCELLED状态
node.waitStatus = Node.CANCELLED;
// 因为当前node是异常节点,如果node是tail的话,就把pred设置为队列尾部
if (node == tail && compareAndSetTail(node, pred)) {
// 将pred的next设置为null,即所有无效节点都出队了
compareAndSetNext(pred, predNext, null);
} else {
// 此处有3个要注意的判断
// 1、(pred != head)
// 如果当前pred不是head并且node也不是tail,即node不是老二节点,也不在队列尾部
// 2、(ws = pred.waitStatus) == Node.SIGNAL ||(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))
// 如果pred的waitStatus状态是SIGNAL那没问题,就算不是SIGNAL反正只要不是CANCELLED,就把他的状态改为SIGNAL
// 3、pred.thread != null
// pred要有对应的线程
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 获取node的下一个节点,并将pred的next设置为node的next,意味着在队列中pred至node这个范围内的所有节点都被剔出队列了
// 这里只是将pred的next设置了,至于前节点当其他线程执行到shouldParkAfterFailedAcquire()时就会处理这个问题了
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 如果node的前节点是head,自己是老二了唤醒队列中下一个可用节点,重点是unparkSuccessor的节点遍历会找到一个正常的节点唤醒,代码请看下面的介绍
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
- 执行了cancelAcquire说明当前加入队列的node应当踢出队列,只有程序异常时才会发生这种情况,因为try中代码是自旋的.
- 释放锁
lock.unlock();
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// ReentrantLock 重新的tryRelease
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) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
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);
}
- 如果节点状态小于0,则初始化为0
- 如果下一个节点是空的,或者状态为CANCELLED(status>0的状态只有一种即CANCELLED),那么就从tail向前找到一个处于阻塞状态状态正常的节点,注意此处是全部遍历,而不是从后向前找到一个就退出
- 唤醒找到的节点,唤醒后的节点会从LockSupport.park(this);处继续运行