AQS基本概念
- aqs全称为AbstractQueuedSynchronizer 是一个抽象同步队列,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch等。
- AQS是一个抽象类,主要是通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取以及释放的方法来提供自定义的同步组件。
AQS核心参数
- Node结点:采用双向链表的形式存放正在等待的线程 waitStatus状态、thread等到锁的线程
- waitStatus状态:
CANCELLED,值为1,表示当前的线程被取消;
SIGNAL,值为-1,释放资源后需唤醒后继节点;
CONDITION,值为-2, 等待condition唤醒;
PROPAGATE,值为-3,工作于共享锁状态,需要向后传播,比如根据资源是否剩余,唤醒后继节点;
值为0,表示当前节点在sync队列中,等待着获取锁。 - Head 头结点:等待队列的头结点
- Tail 尾结点:正在等待的线程
- State:锁的状态 0 (无锁)、1(有线程获取到锁), 当前线程重入不断+1(重入锁)
- exclusiveOwnerThread:记录锁的持有线程
AQS与ReentrantLock
ReentrantLock是一种基于AQS框架的应用实现 。在ReentrantLock在默认的情况下为非公平锁。
NonfairSync 非公平锁
//NonfairSync
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
FairSync 公平锁
//FairSync
final void lock() {
acquire(1);
}
ReentrantLock源码分析
lock()
public static void main(String[] args) {
Lock lock = new ReentrantLock();
lock.lock();
new Thread(()->{
lock.lock();
}).start();
}
final void lock() {
//当锁被释放或者第一次拿到时会修改成功,并且不会创建双向链表
if (compareAndSetState(0, 1))
//存储拿到锁的当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
//tryAcquire()底层当前线程会继续拿锁,如果成功返回true
if (!tryAcquire(arg) &&
//Node.EXCLUSIVE:一个空的Node
//addWaiter()
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//tryAcquire最终调用这个方法,目的:当前线程在进行重试
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//getState()得到aqs的状态值
//如果为1表示锁被线程占有,0则表示锁没有被占有
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
//存储拿到锁的当前线程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//lock实现重入锁
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
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;
//把node变成链表的尾节点
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) { // Must initialize
//创建一个头节点,因为空数据所以头节点等于尾节点
if (compareAndSetHead(new Node()))
//头节点不存放线程
tail = head;
//继续循环
} else {
node.prev = t;
//当前node变为尾节点
if (compareAndSetTail(t, node)) {
//双向链表所以相互关联
t.next = node;
return t;
}
}
}
}
为什么头节点是空的?
因为exclusiveOwnerThread中已经存放了线程,所以头节点不用在存储主线程,减少占用的内存。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//拿到节点的上一个节点
final Node p = node.predecessor();
//如果p是头节点并且拿到锁
//如果此时释放锁时会唤醒头节点的下一个节点
//在parkAndCheckInterrupt()中阻塞的线程会重新运行到这一行
//只有当前节点是头节点的下一个才可以重试获取锁
if (p == head && tryAcquire(arg)) {
//让头节点的下一个节点变为头节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//上一个节点waitStatus为-1返回true,其它返回false
//因为是死循环所以返回false会执行执行这里,此时waitStatus变为-1,
//所以执行parkAndCheckInterrupt()阻塞当前线程
//这里之所以不直接返回true是因为aqs会被很多并发包类库使用,会有别的用途
if (shouldParkAfterFailedAcquire(p, node) &&
//使用LockSupport.park(this)阻塞当前线程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 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.
*/
//将pred节点的waitStatus值改为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
//阻塞当前线程
LockSupport.park(this);
return Thread.interrupted();
}
unlock()
当调用两次lock(),需要在调用两次unlock()因为重入锁status为2,当status为0时才会释放锁。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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;
//exclusiveOwnerThread修改为null
setExclusiveOwnerThread(null);
}
//可能发生了重入,所以修改state值
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
//头节点waitStatus改为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);
}
总结
当执行lock()时首先会进行cas操作,成功的话把当前的线程存到exclusiveOwnerThread中。(注意:公平锁的话是没有这一步的)如果上锁失败的话会比对是否是当前线程,是的话为重入锁,会让status值+1。
如果exclusiveOwnerThread中存储的不是当前线程,会把当前的线程加到尾节点,如果还没有创建阻塞队列的话会创建队列(头节点中不会存储线程)。然后因为节点创建时waitStatus为0,会进行CAS操作变成-1。在会调用LockSupport.park(this)方法让当前线程变为阻塞等待的状态(等待许可),然后等拿到锁的线程释放锁时,会判断当前节点的上一个节点是否为头节点。
如果是并且拿到锁会将当前节点变为头节点,把当前节点的线程存到exclusiveOwnerThread中。
unlock()会先让state-1判断是不是为0,不为0说明是重入锁直接返回false,不会释放锁。然后为0的话就会释放头节点的下一个节点的线程。