J.U.C之AQS
一、AQS简介
AQS的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用 getState()、setState(int) 和 compareAndSetState(int, int) 方法来操作以原子方式更新的 int 值。
AQS会把所有请求线程构成一个CLH队列,当一个线程执行完后(如解锁后lock.unlock)会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,经过调查线程的显式阻塞是通过调用LockSupport.park完成,而LockSupport.park则调用sun.misc.Unsafe.park本地方法,再进一步,HotSpot在Linux中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。
该队列如图:
与synchronized相同的是,这也是一个虚拟队列,不存在队列实例,仅存在节点之间的前后关系,通过head,tail,next来关联。当有线程竞争琐时,该线程会首先尝试获得锁,这对于那些已经在队列中的线程来说就不公平,这也是非公平锁的由来,但是这样会提高吞吐量。如果已存在Running线程,则新的竞争线程会被追加到队尾,采用基于CAS的Lock-Free算法。
二、ReentrantLock的调用过程
时序图:
三、源码分析
加锁过程:
1. Lock.lcok:
public void lock() { sync.lock(); } final static class NonfairSync extends Sync { //非公平锁的lock方法 final void lock() { if (compareAndSetState(0, 1)) //A setExclusiveOwnerThread(Thread.currentThread()); //B else acquire(1);//C } } |
A: CAS设置state为1,0表示没有任何线程持有锁
B: 如果CAS成功,表示拿到了锁,则设定当前线程为锁的所有者
C: 如果CAS失败,表示获取锁失败,则再次请求锁,如果失败则加入等待队列,并设置为阻塞状态,知道被中断或有线程释放锁时被唤醒。
2. NonfairSync .Acquire:实际上是AQS的模板方法
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } |
2.1 tryAcquire : NonfairSync .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) { //A if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) {//B int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; //C } |
A: 如果state=0,表示锁空闲,则CAS尝试获取锁,如果成功,设置锁的所有者为自己,返回true
B:如果state!=0,表示锁已经被别的线程拿到了,判断此时锁的持有者是不是自身,如果是,说明可以重入,这就是ReentrantLock的可重入特性,因为此时没有竞争,直接令state加一,返回true。这也就是每次ReentrantLock重入都会加一的核心地方,之后的每次unlock就会-1.
C. 如果state!=0,且锁不是自己拿到的,则返回false,说明再次尝试获取锁失败。获取失败后!tryAcquire(arg)方法返回true,就进入了后续的“与”代码。
2.2 addWaiter:AQS的模板方法
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) { //A如果队尾已存在 node.prev = pred; //B把新节点挂在队尾节点后边 if (compareAndSetTail(pred, node)) { //CAS 设置tail节点为新节点,防止两个线程同时执行了B,这种情况tail就挂了两个后续节点 pred.next = node; return node; } } enq(node); //如果tail为空,或者CAS失败,则直接入队,继续CAS return node; } |
private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize,如果tail为空, Node h = new Node(); // Dummy header,构造哑节点 h.next = node; node.prev = h; if (compareAndSetHead(h)) { //设置head节点 tail = node; return h; } } else { //如果tail非空 node.prev = t; //新节点的前指针指向tail if (compareAndSetTail(t, node)) { //tail指向node节点 t.next = node; //t的next指向新节点 return t; } } } } |
通过enq保证如果有多个线程addWaiter加入队尾时CAS失败能继续加入。
成功加入CLH队列后,就继续执行acquireQueued方法,让其阻塞
2.3 acquireQueued:AQS的模板方法,设置新入队的线程阻塞
final boolean acquireQueued(final Node node, int arg) { try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 取当前线程的前置节点 if (p == head && tryAcquire(arg)) { //如果是头节点,尝试获取锁 setHead(node); //获取锁成功,则当前节点设为头结点 p.next = null; // help GC return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) //阻塞线程的精髓 interrupted = true; } } catch (RuntimeException ex) { cancelAcquire(node); throw ex; } } private void setHead(Node node) { head = node; node.thread = null; node.prev = null; } |
2.3.1:shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int s = pred.waitStatus; if (s < 0) //A /* * This node has already set status asking a release * to signal it, so it can safely park */ return true; if (s > 0) {//B /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } Else //C /* * 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, 0, Node.SIGNAL); return false; } Node的状态: 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; |
A: 如果前继节点状态为signal,表明当前节点需要unpark,返回成功,则parkAndCheckInterrupt方法会让线程阻塞
B:如果前继节点状态为 CANCELLED(ws>0),说明前置节点已经被废弃,则回溯到一个非取消的前继节点,返回false,然后继续轮询,直到A返回ture,线程阻塞。
C:如果为其他状态,则设置前继节点状态位Signal,返回false,继续轮询。这里重试了两次,第一次标记为signal,准备阻塞,但是还不做阻塞的操作,重试一次如果不成功获得锁则真正阻塞。
此方法主要是根据前继节点来判断当前线程是否应该被阻塞的,如果前继节点处于CANCELLED状态,则删除这些节点重新构造新队列。
解锁过程:
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; } |
ReentrantLock.sync.tryRealease:
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { //只有state=0时,才返回true,才说明完全释放了锁 free = true; setExclusiveOwnerThread(null); } setState(c); return free; } |
AQS.unparkSuccessor:
private void unparkSuccessor(Node node) { /* * Try to clear status in anticipation of signalling. It is * OK if this fails or if status is changed by waiting thread. */ compareAndSetWaitStatus(node, Node.SIGNAL, 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. 如果当前节点被取消或指向空,从tail节点开始遍历,找到非取消状态的节点 */ 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); } |
四、总结
在基于AQS构建的同步器类中,最基本的操作包括各种形式的获取操作和释放操作。获取操作是一种依赖状态的操作,并且通常会阻塞。当使用锁或信号量时,“获取”操作的含义就很直观,即获取的是锁或者许可,并且调用者可能会一直等待直到同步器类处于可被直接获取的状态。在使用CountDownLatch时,“获取”操作意味着“等待并直到闭锁达到结束状态”,而使用FutureTask时,则意味着“等待并直到任务已经完成”。“释放”并不是一个可阻塞的操作,当执行“释放”操作时,所有在请求时被阻塞的线程都会开始执行。
如果一个类想成为状态依赖的类,那么它必须拥有一些状态。AQS负责管理同步器类中的状态,它管理了一个整数状态信息,可以通过getState,setState以及compareAndSetState等protected类型方法来进行操作。这个整数可以用于表示任意状态。例如,ReentrantLock用它来表示所有者线程已经重复获取该锁的次数,Semaphore用它来表示剩余的许可数量,FutureTask用它来表示任务的状态。
AQS中获取操作和释放操作的标准形式:
Boolean acquire() throws InterruptedException{ While(当前状态不允许获取操作){ if(需要阻塞获取请求){ 如果当前线程不在队列中,则将其插入队列 阻塞当前线程 } else { 返回失败 } } 可能更新同步器的状态 如果线程位于队列中,则将其移出队列 返回成功 } Void release() { 更新同步器的状态 If(新的状态允许某个被阻塞的线程获取成功) 解除队列中一个或多个线程的阻塞状态 } |
根据同步器的不同,获取操作可以是一种独占操作(如ReentrantLock),也可以是一个非独占操作(如Semaphore和CountDownLatch)。如果支持独占,需要实现一些保护方法,包括tryAcquire、tryRelease和isHeldExclusively等,而对于支持共享的同步器,则应该实现tryAcquireShared和tryReleaseShared等。
为了将此类用作同步器的基础,需要适当地重新定义以下方法,这是通过使用 getState()、setState(int) 和/或 compareAndSetState(int, int) 方法来检查和/或修改同步状态来实现的:
默认情况下,每个方法都抛出
参考:http://suo.iteye.com/blog/1329460 《Java并发编程实战》