写在前面: 本文源码基于jdk11来举例,AQS在jdk8和jdk9有了一些变化,但是不影响学习核心源码和设计思想。
首先看一下AQS即AbstractQueuedSynchronizer
的类图:
可以看到之前整理的大部分锁都是基于AQS实现的。
AQS核心
首先看AQS的属性:
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
复制代码
这里的state
所代表的意思是随子类来定义的,以ReentrantLock
为例,0表示没有被占用,当有线程持有该锁则变为1。由于ReentrantLock
是可重入锁,所以每次重入时state
还可以继续加1。
AQS中维护着一个双向的阻塞队列,如下图所示:
AQS维护的是一个名为CLH(Craig, Landin, and Hagersten)队列。再回过头看head
和tail
,head
表示头节点。源码中有这样一句注释:To enqueue into a CLH lock, you atomically splice it in as new tail. To dequeue, you just set the head field.
,意思是如果要排队进入CLH队列则需要原子地设置为tail
,如果要退出队列则只要设置为head
即可,可见CLH队列并不包含head
节点。
AQS源码
我们以ReentrantLock
非公平锁为例来了解源码:
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
void test() {
try {
lock.lock();
// 业务代码
} finally {
lock.unlock();
}
}
}
复制代码
进入lock
方法:
public void lock() {
sync.acquire(1);
}
复制代码
再跟进到sync.acquire(1)
里
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
跟进到tryAcquire(arg)里又调用了nonfairTrytAcquire(acquires)
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
复制代码
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 拿到state
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;
}
复制代码
如果tryAcquire(arg)
返回true则表示没有线程在等待锁或重入锁,后续操作就不用再做了。如果没拿到锁,则需要调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
方法。从名字上来猜一下,这里是用排他的形式将当前线程扔到阻塞队列里去。具体先看一下addWaiter(Node.EXCLUSIVE)
方法:
private Node addWaiter(Node mode) {
// 获取当前要加进来的线程节点
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) { // 尾部节点存在则把新节点放在末端
// 从名字上看,新节点的prev节点设置为老的尾节点
node.setPrevRelaxed(oldTail);
// 用CAS操作把新节点设置为tail
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
复制代码
源码读到这儿可以稍微总结一下,AQS核心就是用CAS去操作head
和tail
。设想一下如果用锁的话,如果要操作head
和tail
需要锁定整个链表。用CAS只需要关注tail
这一个节点就可以了,这也是为什么AQS效率很高。
为什么是双向列表呢?因为添加一个线程节点时需要看一下前置节点的状态,如果前置节点是在持有中,那么新节点就需要在后面等着。如果前置节点已经取消了,那新节点就要越过该节点。整个过程需要查看前置节点的状态,所以必须是双向的。
返回节点后接着调用acquireQueued
方法:
// 此时参数中的node已经进入阻塞队列
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();
// p为head说明node是阻塞队列中的第一个节点
// node试着枪锁
if (p == head && tryAcquire(arg)) {
// node枪锁成功,设置node为head
setHead(node);
p.next = null; // help GC
return interrupted;
}
// node不是阻塞队列第一个节点或抢锁失败
if (shouldParkAfterFailedAcquire(p, node))
// 挂起当前线程等待被唤醒
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);
if (interrupted)
selfInterrupt();
throw t;
}
}
复制代码
接着看shouldParkAfterFailedAcquire
方法:
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) {
// 前驱节点取消排队,让当前节点的prev指向正常的前驱节点,好让它来唤醒自己
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 此时状态只可能是0、-2和-3,但前面的源码都没有设置过waitStatus,所以此时状态应该是和刚设置成tail一样,即0
// 用CAS将前驱节点waitStatus设置为-1
/*
* 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.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
复制代码
// TODO unlock过程
VarHandle
addWaiter
方法中的node.setPrevRelaxed(oldTail)
意思是把当前节点的前置节点设置成老的tail
:
private static final VarHandle PREV;
final void setPrevRelaxed(Node p) {
PREV.set(this, p);
}
复制代码
假如有这么段代码Object o = new Object()
,即o
指向了了内存中的new Object()
,VarHandle
指的就是这个引用。但是已经有了对象o
了,为什么还要VarHandle
呢?
public class TestVarHandle {
int x = 8;
private static VarHandle handle;
static {
try {
handle = MethodHandles.lookup().findVarHandle(TestVarHandle.class, "x", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestVarHandle t = new TestVarHandle();
System.out.println((int)handle.get(t));
handle.set(t,9);
System.out.println(t.x);
handle.compareAndSet(t, 9, 10);
System.out.println(t.x);
handle.getAndAdd(t, 10);
System.out.println(t.x);
}
}
复制代码
上述代码对VarHandle
举了几个简单的例子,可以看到它可以操作类的成员变量以及提供一些原子的操作。其实VarHandle
的出现主要是替换UnSafe
类,UnSafe
对于普通程序员来说用起来太危险了。VarHandle
也可以代替反射,可以比反射效率高出不少。