JDK13 AQS源码解读:

410 阅读8分钟

AbstractQueuedSynchronizer队列同步器,用来管理多线程竞争共享资源场景并发安全。常见的同步器如ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore底层都依赖该api。这篇结合可重入锁ReentrantLock来解读其源码对ASQ的实现。

//默认ReentrantLock为非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}

//new ReentrantLock时,传入true 则ReentrantLock为公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

//////////////////////////////先看ReentrantLock作为默认非公平锁实现,再看其作公平锁实现/////////////////////////////

//java.util.concurrent.locks.ReentrantLock#lock
//子线程内调用Lock.lock(),后面讲解中调用该方法线程可能中断
public void lock() {
    //争夺锁,sync为ReentrantLock内部维护的AQS实现,调用AQS.acquire(1)
    sync.acquire(1);
}

//java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
    //当tryAcquire(arg)返回false(代表抢夺锁失败),acquireQueued(node,arg)返回true(代表应该被中断)时,才会触发selfInterrupt()中断
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

上面争夺锁代码中,依次调用了tryAcquire(arg)、addWaiter(Node.EXCLUSIVE)、acquireQueued(node, arg),AQS将线程包装为Node。下面来看这3个api:

    //非公平NonfairSync#tryAcquire
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    //先看tryAcquire(arg):
    //非公平模式竞争锁
    //java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        
        //锁,拿state当作锁,+1代表上一把锁,+2代表上两把锁也就是重入,volatile变量state,state=0时代表资源未被任何线程占用,可直接抢到锁;释放锁时cas-1
        int c = getState();
        if (c == 0) {
            //初次上锁,cas对state+1,此时其他线程也可能来到这里,故需要cas运算
            if (compareAndSetState(0, acquires)) {
                //AQS指向当前线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        
        //如果c>0并且AQS中记录的线程就是当前线程
        else if (current == getExclusiveOwnerThread()) {
            //current线程内对state+1,此时为单线程current内操作,故不需要cas运算
            int nextc = c + acquires;
            
            //state不能超出int最大值
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            
            //对state+1后重新赋值给state,可以看到通过用volatile字段state作为锁,每次重入时对其+1作为新的锁对象,体现了其可重入锁特性
            setState(nextc);
            return true;
        }
        //如果抢夺锁成功返回true,抢夺锁失败则返回false
        return false;
    }


    //再来看addWaiter(Node.EXCLUSIVE):
    //当上一步tryAcquire(arg)返回false时,即抢夺锁失败时才会进入该方法
    //传入Node模式:static final Node EXCLUSIVE = null , 代表独占模式
    private Node addWaiter(Node mode) {
        //将当前争夺锁失败的线程包装为Node,并传入null
        Node node = new Node(mode);
        //注意这里有个死循环
        for (;;) {
            //AbstractQueuedSynchronizer#tail:AQS中维护一个队尾节点Node,初始为null
            //private transient volatile Node tail
            Node oldTail = tail;
            
            //同步队列中存在其他线程在排队
            if (oldTail != null) {
                //当前线程对应的Node设置prev指向原来的tail,多线程争夺锁失败时,每个待入队线程会将prev指向为队列中的tail节点;队列初始后将prev指向当前占用锁的线程
                node.setPrevRelaxed(oldTail);
                
                //多线程下,当前线程将tail节点cas运算设置为当前node节点。如果队列只存在自身一个元素,next指向的还是自己,否则指向下一个新Node节点,并返回next指向的节点。
                if (compareAndSetTail(oldTail, node)) {
                
                    //oldTail指针指向新的队尾Node,即当前线程
                    oldTail.next = node;
                    
                    //返回新的node节点
                    return node;
                }
            } else {
                //初始抢夺锁时,队列尾Node为null时,代表不存在等待线程,需要初始化队列。入队的Node节点head、tail都指向自身,重复上面for循环调用node.setPrevRelaxed,同样将prev指向自身
                initializeSyncQueue();
            }
        }
    }
    
    //初始化同步队列,这个队列就是由pre next指针构建的一个双向Node链表
    private final void initializeSyncQueue() {
        Node h;
        //初始化时,将head、tail都指向new Node()。即初始化队列时,链表只存在一个节点,这个节点代码当前占用锁的线程
        if (HEAD.compareAndSet(this, null, (h = new Node())))
            tail = h;
    }


//再来看acquireQueued(addWaiter(node, arg),传入的node即为上一步新建的node尾节点。此时争夺锁失败的线程已经放入AQS维护的链表上,并且当前线程Node存在链表尾,并且和其前驱链表节点存在一个双向指向,即前一个Node next 指向链尾当前线程Node,当前线程Node prev 指向链表上其前驱节点。
final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        for (;;) {
            //获取当前线程Node其prev指针指向的Node p,上面讨论过,队列如果只存在Node,prev拿到的便是自身Node
            final Node p = node.predecessor();
            
            //如果p为链表头结点,即当前线程Node前一个 Node节点为head节点时,尝试抢夺锁(非公平体现)。当队列存在多个Node时,第一个Node的prev指向自身,第二个Node的prev指向第一个Node,那么会出现,这两个Node的head指针都指向head结点,那么他们两个都会抢锁。谁抢成功后,将自身设为head节点,因此head节点实际上代表抢到锁的线程,当它释放锁后会去唤醒后续中断线程,而当它释放锁了新来的Node同样可以抢夺(非公平提现)
            if (p == head && tryAcquire(arg)) {
                //如果抢到锁,将当前线程置为头结点
                setHead(node);
                
                //加锁成功后,将head节点的next指向null
                p.next = null; // help GC
                
                //抢到锁,返回false,无需进入selfInterrupt()中断
                //当interrupted为true时,进入selfInterrupt()开始中断
                return interrupted;
            }
            
            //如果当前线程Node入队时,它的prev节点不是head结点又或者是head节点但是没抢到锁,则应该挂起
            if (shouldParkAfterFailedAcquire(p, node))
                //false | true = false
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        if (interrupted)
            selfInterrupt();
        throw t;
    }
}


//java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire
//传入的node为队列尾节点,并且该node的prev不是头结点或者该node的prev是头结点但尝试抢夺锁失败。传入的p为该node节点的head节点。对于没抢到锁的Node,执行shouldParkAfterFailedAcquire
//head下一个线程抢锁失败会执行该方法。之后的Node会直接执行该方法。
//传入的参数中,对于队列前两个Node,pred总是head节点,而队列非前两个Node,pred总是非head节点。node则代表当前线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //初始值为0
    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.
         */
         //for(;;)第一次将prev节点ws改为Node.SIGNAL(处于该状态时,需要收到一个Signal信号才会unpark),再次调用时,直接返回true。之后node节点prev不是head节点或者是head但抢锁失败,则挂起。即当当前线程发现它的prev节点处于Node.SIGNAL状态时,会执行挂起并检查是否已经中断。
        return true;
        
    //在AbstractQueuedLongSynchronizer#cancelAcquire取消抢夺锁方法中,node.waitStatus = Node.CANCELLED ,将node waitStatus=Node.CANCELLED(值为1)。
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        //如果node节点的prev节点取消抢夺了,把它的prev节点的prev节点当做它的prev节点,并将node的prev节点的next指向node(建立双向关系)。while循环一直排除之前取消竞争的prev节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
         //第一次,将prev节点的waitStatus cas设置为Node.SIGNAL,然后return false
         //如果队列中只存在一个Node,那么将自身的ws设置为Node.SIGNAL,通过for循环再次从acquireQueued方法开始执行再次去抢锁。抢到之后无需中断,没抢到再进入shouldParkAfterFailedAcquire,此时它的ws=-1,进入if(ws == Node.SIGNAL)条件语句
        //如果队列中存在多个Node节点,那么将其prev指向的节点ws设置为Node.SIGNAL,并返回false,重复循环。再从从acquireQueued方法开始执行再次去抢锁。抢到之后无需中断,没抢到再进入shouldParkAfterFailedAcquire,同样会挂起.
        pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
    }
    return false;
}

接下来,来看抢锁失败之后,shouldParkAfterFailedAcquire返回true之后调用parkAndCheckInterrupt()进行挂起和中断检查方法:

//AbstractQueuedSynchronizer#parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
    //挂起
    LockSupport.park(this);
    //被打断时返回false,没被打断则返回true
    return Thread.interrupted();
}

//当parkAndCheckInterrupt返回true时,false | true =true,当前子线程应该执行该中断方法,相当于二次中断检查。
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

//////////////////////////以上便是Lock.lock()尝试加锁实现流程。接下来看Lock.unLock()://////////////////////////////

//java.util.concurrent.locks.ReentrantLock#unlock
//子线程内调用Lock.unLock()
public void unlock() {
    //释放锁,sync为ReentrantLock内部维护的AQS实现,调用AQS.release(1)
    sync.release(1);
}

public final boolean release(int arg) {
    //释放锁
    //如果tryRelease返回true,代表当前线程完全释放锁
    if (tryRelease(arg)) {
        //拿到head节点
        Node h = head;
        //上面加锁阶段,acquireQueued调用时,ws默认为0,此时并未挂起。如果这里head节点ws则无需unparkSuccessor(h),否则进入传入head节点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    ////state-release,如果release>1代表释放多把锁,只有state=0时才真正放弃占有锁
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    //如果state==0,将AQS内部包装的当前线程置为null,并将free标识置为true,否则仍然返回false并将state-release
    setState(c);
    return free;
}

//传入head节点
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    //如果ws为Node.SIGNAL,将node.ws cas运算置为0
    if (ws < 0)
        node.compareAndSetWaitStatus(ws, 0);

    Node s = node.next;
    
    //如果head节点的next节点==null或者已经cancel,从链表尾从后往前开始遍历得到离head节点最近的可唤醒的Node,作为head节点的next,排除ws=1或者node为null的节点
    if (s == null || s.waitStatus > 0) {
        s = null;
        //只要不是head节点,继续for循环
        for (Node p = tail; p != node && p != null; p = p.prev)
            if (p.waitStatus <= 0)
                s = p;
    }
    
    //对得到的Node节点执行unpark()解除挂起,相当于发送一个Signal信号让其解除挂起,之后该节点线程拿到锁得到CPU执行权进入代码块,其他线程仍然在队列中中断,等待其释放锁。
    if (s != null)
        LockSupport.unpark(s.thread);
}