JUC | AQS 源码

AQS-CLH

AQS 是一个抽象的类,以在 ReentrantLock 中的实现为例学习一下 AQS 的相关操作。

AQS 中维护了一个 volatile int state 的变量,这个 state 代表的是临界资源,还有 FIFO 的线程等待队列。

state 在不同实现中代表的意思可能不同,在 ReentrantLock 中 state=1 表示已经被锁定,此时其他线程再尝试加锁会失败,失败的线程会被放入线程队列中,同时 ReentrantLock 是可重入锁,表示获得一把锁的线程可以再次获取到这把锁,只需要 state + 1 即可,同样解锁的时候需要 state -1 。

AQS 的结构:

175731

第一个线程 - 获取锁 -> 成功

第一个线程可以成功的获取到锁。然后设置这个线程为独占线程

235953

此时,如果再有第二个线程来获取锁时,由于是非公平锁,所以上来先尝试争抢锁,线程1还没有释放锁,因此争抢失败。

    public final void acquire(int arg){
        if(!tryAcquire(arg)&&//线程2 尝试争抢锁失败
        // addWaiter 返回一个包含当前线程的 node 节点,实际也是链表的尾部节点。
        acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
        selfInterrupt();
        }

如果当前节点的前置节点是 head 就尝试争抢锁,如果抢到就把当前节点设置为 head 节点,如果前置节点不是 head 或者 争抢锁失败,就会通过 shouldParkAfterFailedAcquire 方法将前置节点的 waitStatus 设置为 -1,再通过 parkAndCheckInterrupt 方法将当前的线程挂起。

    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);
        }
        }

在 ReentrantLock 中,如果争抢锁的线程仍然是获取锁的这个线程,那么是可以成功获取到锁的,只是 stata 的值会 + 1。

第二个线程 - 获取锁 -> 失败

线程二执行 tryAcquire() 后会返回 false,接着执行 addWaiter(Node.EXCLUSIVE) 逻辑,将自己加入到一个 FIFO 等待队列中。addWaiter() 方法执行完后,会返回当前线程创建的节点信息。继续往后执行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 逻辑。

第三个线程 - 获取锁 -> 失败

线程三执行 tryAcquire() 也会返回 false,接着执行 addWaiter() 逻辑,此时等待队列的tail节点指向线程二,进入if 逻辑后,通过CAS指令将tail节点重新指向线程三。接着线程三调用enq()方法执行入队操作,和上面线程二执行方式是一致的。

第一个线程 - 释放锁

首先是 线程一 释放锁,释放锁后会唤醒 head 节点的后置节点,也就是我们现在的线程二,具体操作流程如下:

214318

释放锁代码:

    public final boolean release(int arg){
        if(tryRelease(arg)){
        Node h=head;
        if(h!=null&&h.waitStatus!=0)
        unparkSuccessor(h);
        return true;
        }
        return false;
        }

这里首先会执行tryRelease()方法,这个方法具体实现在ReentrantLock中,如果tryRelease执行成功,则继续判断head 节点的waitStatus是否为0,前面我们已经看到过,headwaitStatueSIGNAL(-1),这里就会执行 unparkSuccessor() 方法来唤醒 head 的后置节点,也就是我们上面图中线程二对应的Node节点。


参考资料一

参考资料二

参考资料三