花了两天时间,终于把AQS的基本原理和源码理了一遍,本不想记下来,但是害怕时间久了就淡忘了,毕竟好记性不如烂笔头,还是开始码字吧。
一、楔子
说起编程的发展,大趋势就是面向过程的编程向面向对象的编程发展的。现在大多数语言都是面向对象的了,究其原因,大多是因为面向对象编程,提供了更高的可扩展性,封装、继承、多态等特性,给面向对象提供了更多的操作空间,精简了更多的重复代码,更加符合我们人类世界的活动。我想还有另外一个重要原因是,随着硬件的升级,现在大多都是多核服务器,多个cpu可以同时运作,那么我们的过程化编程就显得有点浪费资源,我们把一个过程拆分到多个模块,由多个cpu同时运作,它不香吗?
具体到java语言,那就是多线程高并发的编程。一说到这里,各位javaer马上脑袋里就反应出了synchronize和lock字样。确实,java里面基本就靠这两个东西实现多线程编程了。多线程是什么?多个线程同时运行,充分利用服务器的计算上限,恨不能把cpu一直干到100%,超级开心。但是,多线程效率高了,那么就会带来相应的一些问题,最首当其冲的就是对共享资源的可见性和安全性。同时多个线程访问同一个资源或者同一个变量,肯定会造成这个资源的误读误写的。你可以想象一下,突然有一天,平时跟你勾心斗角的同事A,嘴角带着一丝邪魅微笑,给你介绍了一个家财万贯,身材婀娜多姿的人美路子野的妙龄少女,你会怎么办?当然是不管三七二十八,花个几万,从头到脚把自己打整干净,贷款60w在高新区整一套公寓,逼着父母给了22w买个马自达cx-5,骚红那种,第二天兴冲冲准备998朵玫瑰去跟这个女孩儿求婚,结果呢?如果这个女孩心地善良,她会直接告诉你她结婚了,麻蛋,花了这么些钱白干了么?家底花完了,每个月还要还1w的房贷车贷,苦啊!!!这还算好的,女孩比较耿直,如果女孩不告诉你她结婚了还跟你睡了一晚,第二天她老公来捉奸在床了,那会怎么办?我不知道,我也没经历过啊。
知道线程安全性的重要了吧,这里的安全不是指的线程不安全,指的是你追的那个白富美(共享资源)不安全啊,一群色狼追着在屁股后面,还想一起上,能安全么?那怎么办呢?给自己加把锁把,你们可以一个个上~~~额,这里请仔细品一下这个上字^_^。
二、修炼内功
市面上的锁太多了,互斥、共享、排他、读写、公平、非公平,andsoon。这些不在我们的讨论范围,我们主要谈谈经常用来和Synchrogazer进行比对的Lock锁吧,我们都知道,Lock是显式锁,需要我们自己加锁然后自己关闭锁,它让我们能够更加灵活多变的对我们的代码进行加锁处理。那么,Lock对代码进行加锁解锁的基本原理是什么呢?它是怎么做到多线程并发访问共享资源时候,保证多个线程能对共享资源进行安全的读写的呢?说道这里,我们就不得不说到我们今天的主角:AQS,如果说Lock是一个武林高手的话,那么AQS就是这个武林高手在爬山时候,失足掉下悬崖,偶然拾得的一本绝世武功的修炼心法。因为java的JUC下面的大多数保证线程安全同步的类,都是基于AQS实现的,包括我们常用的Lock。
那么,AQS究竟是什么东西呢?它是AbstractQueuedSynchronizer的缩写,见名思意,Abstract表明它是一个抽象类,也可以看成它是一系列保证线程安全性的方法和概念的抽象。Queued,我们一眼就能看出,AQS里面肯定会用到了队列,不要问我为什么知道,作为一个征战多年的javaer,这不就类似于一看到91开头的网址,我们的脑海里就出现了宾馆的床么。但是AQS里面的队列有点不一样,它是用了CLH同步锁队列,这是一个双向的队列,有头有尾,方便我们阻塞 和唤醒里面的线程,并对已经中断的线程进行清除。当然这个队列的节点并不是一个个的线程,而是包含了线程 引用的node节点。Synchronizer,表明了我们的AQS它是一个用于线程同步的东西,主要的功能是保证线程的安全同步。
那么,它是怎么做到线程同步安全的呢?首先,它维护了一个int类型的同步状态码,这个状态码是volatile的,保证了每次这个码改变,其他线程能get到。这个状态码牛逼了,在独占锁里,它能表示当前对象是否加锁,加了几把锁(重入了几次),在读写锁里,它能用高位表示读锁的情况,低位表示写锁的占有情况,厉害吧,很多看似简单的东西,其实它是最核心的东西,大道至简嘛。就这么一个简单的volatile int state,就是我们武林秘籍最重要的两个心法之一,另外一个至关重要的心法就是里面维护的CLH队列了。
我们简单看一下,具体的线程是怎么被AQS控制执行的吧。首先,代码里面lock或者什么其他加锁得分方法,都表示哥们儿要开始加锁了,要独自品尝白富美了。但是这个世界告诉你,等等,白富美不是那么好尝的。你要先武装一下自己,把自己打扮成一个node,这才具备了追求白富美的基本条件,然后,进入队列里面等待这个世界和白富美的召唤。当然,作为一个不甘寂寞的男人,你肯定不能这么坐以待毙,你会一直主动想要获取白富美手中的锁,一旦我们的神奇的state变为0,我们里面抢占锁。这就是我们的CAS了,主动出击总好于坐以待毙是不。node里面维护了一个等待状态waitstatus,当它大于0的时候,意味着这个哥们儿跑了,熬不住了,线程取消了。如果它等于-1,那就好办了,说明我已经取到号了,这里得多说一句,现在的社会不像以前,排个队就能办事儿了。信息社会,你排好队了不行,还得要网上取到号,没有 号也不行,这个-1就表示你取了号,处于随时可以办事的状态,否则,就算排队拍到你了,你也得等着,先取到号再来吧。
三、下山历练
我们学习技术,就跟做人一样,最怕的是什么?"道理我都懂,可我依然过不好我的一生"。学习技术,原理你都懂了,最多能应付面试,在实际工作中,我们还得要落实到代码上面去,才能完成我们的任务。那么,我们一起看一下AQS的具体代码吧。
我们就用常用到的ReentrantLock锁来举例跟一下代码吧。我们都知道,在用ReentrantLock的时候,首先调用的是它的lock加锁方法,我吗看一下lock():
public void lock() {
sync.lock();
}
很简单的一个sync的lock 方法调用,那么这个sync是什么呢?
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
// 这里实现的非公平获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果当前线程正拥有锁,比如递归调用的时候,AQS状态码加1实现可重入锁
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;
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();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
}
这个sync其实是一个内部类,它继承自我们的AbstractQueuedSynchronizer类,实现了其中的几个特定方法。其实JUC包下面的大多数同步类,都是采用的这种适配器设计模式,类的内部定义一个类继承自其它某一个类,这样我们可以调用其它类的方法达到继承方法的目的。锁队列的安全加入、获取锁、线程的执行到最后的锁释放,AQS都帮我们完成了,AQS的子类只需要实现他的几个特定的用于获取和释放锁方法,这里又使用的是模板方法设计模式。我们接着看代码:
// 非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
// CAS设置状态从0到1,如果设置成功,表示获取到锁
if (compareAndSetState(0, 1))
// 设置锁的拥有者线程为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 调用AQS公用方法尝试获取锁,失败则加入CLH锁队列
acquire(1);
}
// 自旋获取锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 新来一个线程,不先去尝试获取锁,而是直接进入CLH锁队列
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 公平非公平锁的区别在这里,公平锁多调用了一个hasQueuedPredecessors方法
if (c == 0) {
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;
}
}
代码中我们可以看到,lock的lock()方法,调用了sync的lock()方法,而这个全局变量sync又有两种实例化方式,根据lock实例化时,传入的参数fair是否为公平锁,分别实例化为不同的sync。
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
这里提一下,公平非公平锁的区别从字面 上就可以区分开来,公平锁是先进先出,也就是先尝试获取锁的线程,会在锁 队列的头部。
我们看到,非公平锁和公平锁,在获取锁的时候,都调用了acquire (),这个方法就实现了从获取锁-》线程-》锁队列元素-》进队列-》 得到锁的整个过程。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先,tryAcquire()方法尝试获取锁,而这个方法,就是AQS没有实现,交由子类自己定义实现的方法,目的是让开发者定义不同类型,不同获取锁方式的锁满足自己项目的需求。此方法就是调用了sync里面的nonfairTryAcquire()方法或者tryAcquire(),具体代码看上面sync部分有注释。
如果竞争锁失败,则通过addWaiter(Node.EXCLUSIVE)方法将当前线程封装成CLH队列的一个node元素。我们先看一下NODE元素是怎样的结构。其实CLH锁队列并不是一个严格意义上的队列,它只是通过node元素定义的前驱后继节点,实现的一个双向链表。除了前后节点,NODE元素还定义了几个int常量,用于保存当前线程的状态值。
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
// 此两个NODE变量用于表明当前队列是共享队列还是独占队列,分别对应共享锁和独占锁
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
// 当前节点的线程已经取消,可能因为线程中断之类的原因,这是唯一一个大于0的状态值,后续代码中
// 会很多地方用来判断当前线程是否可用
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
// 登记取号状态,表示后续节点的线程处于随时可被唤醒的状态
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
// 锁是否共享
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
将线程封装为node之后,调用acquireQueued()方法,首先尝试获取锁,注意,这里用到了自旋获取锁for(;;)。
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
* 自旋尝试获取锁
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
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);
}
}
如果获取锁失败,则调用方法shouldParkAfterFailedAcquire(),设置节点的状态为signal,如果前置节点的状态为cancel,则改变前置节点为前置节点的前置节点,此方法为了保证前置节点状态是signal,这样在以后获取锁时,本节点才能被unpark唤醒,如果前置节点状态非signal,后续节点是没法被唤醒的。随后调用parkAndCheckInterrupt()方法阻塞当前线程并返回线程状态。
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
我们注意到,acquireQueued方法的finally块里面,调用了cancelAcquire方法,此处是为了保证,如果自旋获取锁失败,则能够将此节点从队列移除,并保证队列其他节点正常获取锁。
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
上面就是获取锁的过程,失败和成功,锁队列所发生的变化。下面我们看一下释放锁的相关代码。释放锁相对比较容易一点。首先还是从我们熟悉的unlock方法看起。
public void unlock() {
sync.release(1);
}
调用了AQS的release方法:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
release方法中,首先调用tryRelease()释放锁资源,释放成功则调用unparkSuccessor()方法唤醒后续节点。
protected final boolean tryRelease(int releases) {
// 状态值减一
int c = getState() - releases;
// 当前线程未持有锁则抛异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 状态值为0,表明当前线程不再持有锁,则返回true,并重置当前锁的持有线程null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
// 这里注意,如果c>0 表示当前为重入锁,当前线程还持有锁,返回false
return free;
}
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
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);
}
至此,下一个节点得到unpark唤醒,无缝衔接上面在线程刚加入时候被park起来,还在自旋获取锁,这里的unparkSuccessor()方法,给自旋画上了完美的句号,自旋节点的前驱节点是head,并且当前state为0,自旋节点可以cas成功,那么当前线程就可以执行本地程序后面的代码了。
四、华山论剑
故事太长,又没有金庸先生的情怀,相信大家也看累了,最后的煮酒论英雄环节,我们就长话短说,一张图结束我们的AQS之旅,祝大家在java开发的路上,越走越深入。
参考内功心法:深入理解AQS、死磕synchronized