Java并发学习(三)-AbstractQueuedSynchronizer

花了一段时间学习了AbstractQueuedSynchronizer,研究了源码以及博客,这篇文章主要以自己的理解去一起学习AQS。

AQS简介

AQS是简写,全称是AbstractQueuedSynchronizer,在java.util.concurrent.locks 包下面,这个类是Java并发中的一个核心类,从名字里面可以看出来,里面维护这一个双向队列(queue)。和synchronize不同,它在Java语言层面实现了锁,而synchronize则是借用了JVM以及操作系统的。所以会说Reentrant更细腻。

与ReentrantLock的关系

前面说过,AQS里面维护着一个队列,那么队列里面又是什么呢?
主要是线程,以及其他属性。
相信很多人都用过Java里面的锁,除了可以用Synchronize,同样可以用ReentrantLock来实现,ReentrantLock就是用AQS来实现的,它继承自AQS,AQS更像一个框架,如果你想实现锁,共享锁或者排他锁,你需要遵循他的规范去继承它,并且重写方法就可以实现,这里可以看我分析ReentrantLock这篇文章。

AQS队列结构

前面说过了,AQS里面维护这一个queue,它是双向的,按照类里面的含义,这条队列是先入先出的,即FIFO。那么里面的Node节点里面又是咋样的呢?

    static final class Node {
        static final Node SHARED = new Node();       /** 默认是共享模式 */
        static final Node EXCLUSIVE = null;          /** 排他模式 */
        static final int CANCELLED =  1;             /** 表示当前线程被取消 */
        static final int SIGNAL    = -1;      //表示当前节点要被进入sync时,的后继节点要被唤醒,也就是unparking 
        static final int CONDITION = -2;   // 表示当前节点在等待condition,也就是在condition队列中。 
        static final int PROPAGATE = -3;       // 表示当前节点的后续的,acquireShared 能够被执行。 
        volatile int waitStatus;          //初始为0 当为0的时候,就处于sync队列中,等待着获取锁。
        volatile Node prev;               //在检查waitStatus情况下,的前一个节点。
        volatile Node next;              //后一个节点,当被出队时就会被gc。
        volatile Thread thread;         //当前执行的线程。
        Node nextWaiter;          //在condition队列的下一个等待着,或者是share模式的下一个。
    }

如上所示,Node节点里面有5个状态:

  • CANCELLED
  • SIGNAL
  • CONDITION
  • PROPAGATE
  • 0

具体的解释在上面代码中已经有体现。
那么这个Node,什么时候会被使用呢?
在用到锁的时候,我们有个基础的理解,如果一个线程获取不到锁,它会怎么办,它会等待继续尝试获取,或者进入挂起进入等待队列(不消耗cpu)。所以应该知道了,这里的Node是用来存储线程的。下面我将通过AQS源码从以下几个方面分析AQS的结构

AQS结构分析

先看AQS的声明:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
}

继承自一个父类AbstractOwnableSynchronizer 而这个父类里面,只是很简单的定义了一个当前拥有排他锁的线程:

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {

    protected AbstractOwnableSynchronizer() { }

    private transient Thread exclusiveOwnerThread;

    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

AQS,负责管理线程状态, 子类主要通过对volatile变量state的操作去决定是否获取锁。然后再决定是否进入队列。

AQS里面的state

AQS里面有个state,它是volatile类型的,它是用来干嘛的呢?
关于volatile,可以看我这篇文章:Java并发学习(二)-JMM
首先思考下,为什么能够判定某一个线程能够获取锁呢?
答案就是这个state,在AQS里面有一些方法,专门用于去给子类重写的:

    ...
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
    ...

这里只是给了一个头,具体有子类去实现,接下来看看ReentrantLock的实现:

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

上面只贴出了ReentrantLock里面的非公平锁的重写,首先调用父类的getState()获取state变量,在尝试用原子性CAS方式compareAndSetState尝试设置state的值,实际上也就是尝试获取锁。设置成功就返回true,失败的话再检查是否为同一个线程 如果同一个线程,则在一定范围下也获取成功。否则获取不成功。
关于非公平锁,可以看我的专门分析Reentrant的文章。
经过了上面的分析,大概可以清楚,AQS是如何控制资源,并且给管理线程获取状态的。

  • volatile类型的state
  • CAS原子性修改

AQS中锁的获取与释放

上一个小结中,主要通过Reentrant为例大致说明了state的作用,接下来看AQS里面获取锁的思想。
AQS给子类提供了两种类型的锁:

  • 排他锁 (EXCLUSIVE)
  • 共享锁 (SHARED)

排他锁

普通获取排他锁的过程:acquire
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

arg为用户自定义子类传回的state变量,由短路与,可以知道,当(tryAcquire)获取失败时候,才会执行&&后面条件判断。
接下来看addWaiter 方法。

    private Node addWaiter(Node mode) {
        //由mode,新建node,排他
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //先尝试一次入队,如果失败,就用自旋方式入队。
        enq(node);
        return node;
    }

    /**
     * 节点入队操作。
     * 插入到队尾
     * 返回插入时候的这个tail尾节点。
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { //如果尾节点为null,就把头结点设为尾节点。
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                //否则就就把node插入到队列最后。
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

由上代码,当获取锁失败,就需要在AQS里面记录嘛,就是插入这条队列,首先通过addWaiter 尝试插入一次,如果失败,则需要以自旋方式进行插入,一定得插入。都是利用CAS方式替换tail。

可中断获取排他锁:doAcquireInterruptibly
    /**
     * 在排他锁模式下,可中断的获取锁。
     * Acquires in exclusive interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
     //如果当前节点是头节点的后继节点,则直接获取(因为先入先出)
     //默认头节点就是当前获取资源的节点。
                    setHead(node);
                    //清除头节点
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                //当符合获取失败的条件
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
            //取消获取,状态设为cancelled。
                cancelAcquire(node);
        }
    }

同样是先通过自旋的方式获取锁,如果自旋多次,仍然没有获取,并且前置节点已经发生了变化,则需要的时候将线程阻塞。具体什么变化呢?
接下来看shouldParkAfterFailedAcquire

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*这个节点已经成功设置了状态,请求获取锁,所以只需要等
            待前屈节点运行玩就会unpark自己,所以可以进行park。
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * 跳过被cancell的。
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * condition或者 在sync,propagate
             * 尝试设为signal。
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

然后进行park操作:

 private final boolean parkAndCheckInterrupt() {
     //调用native方法进行park
        LockSupport.park(this);
        return Thread.interrupted();
    }

如果获取失败,则需要cancell,看cancelAcquire ,代码比较复杂,看慢慢分析:

    /**
     * 当获取不到就会取消
     *一旦发生异常,导致获取锁失败,则会调用cancelAcquire()方法"Cancels an ongoing attempt to acquire"。
     *  node是tail
     *  node是head
     *  node既不是tail,又不是head
     * @param node the node
     */
    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;
        //1. node不再关联到任何线程
        node.thread = null;
        //2. 跳过被cancel的前继node,找到一个有效的前继节点pred
        // 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;
        //3. 将node的waitStatus置为CANCELLED
        // 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;
        //4. 如果node是tail,更新tail为pred,并使pred.next指向null
        // 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;
            //5. 如果node既不是tail,又不是head的后继节点
            //则将node的前继节点的waitStatus置为SIGNAL
            //并使node的前继节点指向node的后继节点(相当于将node从队列中删掉了)
            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 {
            //6. 如果node是head的后继节点,则直接唤醒node的后继节点
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }

上述cancelAcquire 一共有6步

  1. node不再关联到任何线程
  2. 跳过被cancel的前继node,找到一个有效的前继节点pred
  3. 将node的waitStatus置为CANCELLED
  4. 如果node是tail,更新tail为pred,并使pred.next指向null
  5. 如果node既不是tail,又不是head的后继节点则将node的前继节点的waitStatus置为SIGNAL并使node的前继节点指向node的后继节点(相当于将node从队列中删掉了)
  6. 如果node是head的后继节点,则直接唤醒node的后继节点
超时获取排它锁doAcquireNanos:

超时获取排它锁中,主要就是添加了nanosTimeout 这个参数,用来判定是否已经过了设定时间,从而执行不同逻辑,其他的都与可中断排它锁一致,就不多说了。

    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
释放排它锁release

有获取锁当然也会有释放所,在ReentrantLock里面使用unlock来实现,这里主要讲当ReentrantLock调用到父类AQS代码时候操作:

    /**
     * 释放排他锁。
     * 首先tryRelese释放标记
     * 然后,排它锁获取的肯定是head出现,此时我只要唤醒(unpack)继任线程就可以了。
     */
    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是由子类重写的方法,目的是为了改变state的值,从而代表释放资源。当释放成功后,unparkSuccessor(h),释放头结点的后继节点。

    /**
     * 如果继任者存在,唤醒继任者开始执行。
     * 如果继任者waitStatus<0,则会将其设为=0进行
     * 如果继任者>0(状态无效为cancelled。),则接着往后面找
     */
    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;
        //如果ws<0,则需要设为0,因为:值为0,表示当前节点在sync队列中,等待着获取锁。
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);


        Node s = node.next;
        //重新找一个successor.
        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);
    }

unparkSuccessor方法的作用就是唤醒继任节点,因为一般当前运行的节点会设为head,所以当要释放的时候,unpark后面这个节点就可以了。
当然需要过滤掉cancelled的节点。

共享锁

共享锁,就是指可以允许多个线程同时对某一资源进行访问,这里会有个最开始的问题,会不会有并发问题?因为AQS只是提供了一个并发的核心框架,具体是否有并发问题,要看子类的自我实现,以及子类工具的用途。
什么意思呢?
就好比说,exclusive模式下,AQS管理一次只能一个线程进行运行,运行完后按照规则传递给后继节点。
而share模式下,AQS管理的则是一次能让多个线程同时运行,可以设置同时进入的数量,运行完后再把排它锁状态往后延伸。

共享锁的普通获取acquireShared

首先看acquireShared方法:

    /**
     * 获取共享锁。
     */
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    /**
     * 获取共享锁。
     * Acquires in shared uninterruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    //首先是这样去尝试获取共享锁。通过volatile变量。
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

和获取排他锁思路基本一致,都是先尝试获取,获取不到在进行park操作,但是还有两个地方不同:

  • int r = tryAcquireShared(arg); if (r >= 0)
  • setHeadAndPropagate(node, r);

第一个是通过子类实现的tryAcquireShared方法去判定是否能获取锁,还记得,最开始acquireShared里面也是执行了这个方法,所以这里是被执行了2次的,最后判断是否能够获取锁是通过r>=0来判断的,所以是个范围,也就印证了这是个共享的锁。

第二个的setHeadAndPropagate,和排它锁(only setHead)不同,主要是为了设置head,以及把共享状态往后穿,大家应该还记得Node节点的waitState属性里面有个Propagate的值-3吧。

    /**
     * 设置头结点。
     * tryAcquireShared执行相关。
     * @param node the node
     * @param propagate the return value from a tryAcquireShared
     * 某个节点被设置为head之后,如果它的后继节点是SHARED状态的,那么将继续通过
     * doReleaseShared方法尝试往后唤醒节点,实现了共享状态的向后传播。
     */
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * 尽力去唤醒下一个节点。
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }
doAcquireSharedInterruptibly以及doAcquireSharedNanos

超时获取以及可中断获取,在获取锁代码逻辑上与acquireShared基本一致,而在interruption以及超时判断上,则与排它锁一致,这里就不重复来说了,可以参看上面文章。

ConditionObject

还记得上面说过,在AQS的Node节点里面,有个waitState值为Condition值为-2.就是代表他在阻塞状态,类似于synchronize里面的wait,而ConditionObject也就可以充当Lock的wait和signal。后续会介绍Condition类具体分析。

先看ConditionObject里面内容:

 public class ConditionObject implements Condition, java.io.Serializable {
        ...
        /** 引用的头节点。 */
        private transient Node firstWaiter;
        /** 引用的尾节点。. */
        private transient Node lastWaiter;
        ...
}

如上,在ConditionObject里面包含了两个重要的字段,都是Node类型,firstWaiter和lastWater;其实ConditionObject和AQS里面那条队列并不是同一条,由代码可以分析出来,请看我下文分析。

当然,里面方法主要就是await和signal。

  • private Node addConditionWaiter(),添加当前线程作为waiter
  • private void doSignal(Node first),唤醒firstWaiter
  • private void doSignalAll(Node first),唤醒,所有waiter
  • private void unlinkCancelledWaiters(),删除cancell的节点
  • public final void signal(),对外唤醒任意一个,主要第一个
  • public final void signalAll(),对外唤醒所有
  • public final void awaitUninterruptibly(),不会中断的等待
  • private int checkInterruptWhileWaiting(Node node),判断等待过程中是否发生了异常
  • private void reportInterruptAfterWait(int interruptMode),等待后重新报告异常
  • public final void await() throws InterruptedException ,对外的等待方法
  • final boolean isOwnedBy(AbstractQueuedSynchronizer sync) ,判断是否为当前线程condition
  • protected final boolean hasWaiters(),是否有waiter。

ConditionObject的主要就是用来阻塞队列以及唤醒队列的,而阻塞队列,一方面是把waitState改为condition,另一方面还需要执行park的native方法,而对于唤醒队列,则是把waitState改为0,执行unpark方法。思路理清楚了,接下来分析两个具体代码:

await方法
      public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            //首先释放锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                //如果不在syncqueue里面,即waitState != 0,阻塞本线程
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    //中断了就直接退出
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                //清楚cancelled的节点
                unlinkCancelledWaiters();
            if (interruptMode != 0)
            //判断,是纪录异常还是抛出
                reportInterruptAfterWait(interruptMode);
        }

如上代码,
1. 首先会addConditionWaiter方法,主要作用就是把本线程添加到condition队列的尾端,注意是根据lastWaiter来判定的,所以添加到的是condition队列,waitState为condition。
2. 其次,fullyRelease来释放当前线程锁,也就是占有的资源。
3. 下一步,利用自旋的方式isOnSyncQueue,判断是否仍然处于等待获取资源状态。如果是的就阻塞本线程,有错误就退出。
4. 最后判断是否有错误,以及是否被设置为抛出错误,ConditionObject里面用THROW_IEREINTERRUPT 来判断。

        /**
         * 重新报告interrupt类型。
         */
        private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }
signal方法

ConditionObject里面有两个signal,一个是有FIFO,唤醒头结点,另一个是signalAll,唤醒所有节点。
接下来看signal方法:

        /**
         * 默认唤醒第一个节点线程。
         */
        public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

如上里面会调用doSignal(first):

        /**
         * 唤醒一个waiter。
         * 放入sync队列。
         * @param first (non-null) the first node on condition queue
         */
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

自旋阻塞式的,直到成功被唤醒,具体细节在transferForSignal(first)方法里面:

    /**
     * 唤醒节点,状态改为0。
     * 唤醒成功的话,就插入队尾并且如果此时队尾元素cancell或者强行设置队尾元素失败,那么就需要唤醒此时队尾元素。
     */
    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         * 设置成功了!
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * 把前一个节点设为signnal。
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
         //放到最后重新排队。
        Node p = enq(node);
        int ws = p.waitStatus;
        //检查前一个节点是否为signal。
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

transferForSignal方法的目的就是讲节点waitState值设为0,并且添加到AQS队列的尾端。

收获

看了多遍源码即博客,才慢慢理解了AQS运行的原理,Doug Lea大佬真是厉害,从AQS里面也读到了两个比较有特色的写法:

  1. 使用&& 和||短路与和短路或去实现if的操作,里面用的真多。
  2. 在一个条件判断中,我发现同样的条件用&&连接了两次,后来知道是为了检查两次二故意做的。因为在并发环境下,很可能多做一次,就得到想要的结果了。
    /**
     * 获取队列第一条线程。
     * 有头结点就判断头结点,没有头结点,就从尾端一直找到头结点。
     * Version of getFirstQueuedThread called when fastpath fails
     */
    private Thread fullGetFirstQueuedThread() {
        Node h, s;
        Thread st;
        //两段判断一模一样,检查了两次
        if (((h = head) != null && (s = h.next) != null &&
             s.prev == head && (st = s.thread) != null) ||
            ((h = head) != null && (s = h.next) != null &&
             s.prev == head && (st = s.thread) != null))
            return st;
        //审查不通过,那就从尾端开始
        Node t = tail;
        Thread firstThread = null;
        while (t != null && t != head) {
            Thread tt = t.thread;
            if (tt != null)
                firstThread = tt;
            t = t.prev;
        }
        return firstThread;
    }

分析有诸多不足之处,如有错误,还请指出~

参考文章:
http://ifeve.com/introduce-abstractqueuedsynchronizer/
https://www.cnblogs.com/leesf456/p/5350186.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值