AQS同步器源码分析

前言

Github:https://github.com/yihonglei/thinking-in-concurrent

一 队列同步器

1、概述   

    AbstractQueuedSynchronizer(队列同步器)简称同步器,是用来构建锁或者其他同步组件的基础框架,

内部维护了一个volatile int state(代表共享资源)变量表示同步状态,通过内置的FIFO队列

完成资源获取线程的排队工作(多线程争用资源被阻塞时会进入此队列)。

    同步器是典型的模板方法设计模式,主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,

主要通过getState()、setState(int newState)和compareAndSetState(int expect, int update)对state来进行线程安全操作。

子类被定义为同步组件的静态内部类,同步器自身没有实现任何接口,它仅仅是定义了若干同步状态,也可以支持共享式地

获取同步状态,这样就可以方便实现不同类型的同步组件,比如CountDownLatch,ReentrantLock等等。

2、AQS定义两种资源共享方式

1)Exclusive(独占式,只有一个线程能执行,如ReentrantLock)。

2)Share(共享式,多个线程可同时执行,如Semaphore、CountDownLatch)。

二 队列同步器方法

1、模板方法

void acquire(int arg)

【独占式】获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,

该方法将会调用重写的tryAcquire(int arg)方法。

void acquireInterruptibly(int arg)

【独占式】与acquire(int)相同,但是该方法响应中断,当线程未获取到同步状态进入同步队列中,如果当前线程被中断,

则该方法会抛出InterruptedException并返回。

boolean tryAcquireNanos(int arg, long nanosTimeout)

【独占式】在acquireInterruptibly(int arg)方法基础上增加超时限制,如果当线程在超时时间内没有获取到同步状态,

那么将会返回false,如果获取到则返回true。

void acquireShard(int arg)

【共享式】获取同步状态,如果当前线程未获取到同步状态,将进入同步队列等待,与独占式获取的主要区别

是在同一时刻可以有多个线程获取同步状态。

void acquireSharedInterruptibly(int arg)

【共享式】与acquiredShared(int arg)相同,该方法响应中断。

boolean tryAcquireSharedNanos(int arg, long nanosTimeout)

【共享式】在acquiredSharedInterruptibly(int arg)基础上增加超时限制。

boolean release(int arg)

【独占式】释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒。

boolean releaseShared(int arg)

【共享式】释放同步状态。

Collection<Thread> getQueuedThreads()

获取等待在同步队列上的线程集合。

2、可重写方法

protected boolean tryAcquire(int arg)

【独占式】获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符号预期,然后再进行CAS设置同步状态。

protected boolean tryRelease(int arg)

【独占式】释放同步状态,等待获取同步状态的线程将有机会获取同步状态。

protected int tryAcquireShared(int arg)

【共享式】获取同步状态,返回大于等于0的值,表示获取成功,否则,获取失败。

protected boolean tryReleaseShared(int arg)

【共享式】释放同步状态。

protected boolean isHeIdExclusively()

当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占。

三 队列同步器源码分析

1、同步队列

    同步器依赖内部的同步队列(一个FIFO双向队列,基于链表实现)来完成同步状态的管理,当前线程获取同步状态失败时,

同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列尾部,同时会阻塞当前线程,

当同步状态释放时,会把头结点的下一个节点中的线程唤醒,再次尝试获取同步状态,其头节点是不存线程的。

    同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点等,

Node是构成同步队列的基础。

1.1 AQS静态内部类Node源码

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        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 */
        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;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        // 节点状态,直接影响线程唤醒
        volatile int waitStatus;

        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         */
        // 上一个节点
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         */
        // 下一个节点
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        // 获取同步状态的线程,被包装在Node节点中,放在阻塞队列里
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         */
        // 下一个等待唤醒的节点
        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;
        }
    }

1.2 Node源码说明

int waitStatus(等待状态):

1)waitStatus为int,默认值为0,所以初始状态值为0;

2)CANCELLED,值为1,由于在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待,

节点进入该状态将不会变化;

3)SIGNAL,值为-1,后继节点的线程处于等待状态,而前驱节点的线程如果释放了同步状态或者被取消,

将会通知后继节点,使后继节点的线程得以运行;

4)CONDITION,值为-2,节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用signal()方法后,

该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中;

5)PROPAGATE,值为-3,表示下次共享式同步状态获取将会无条件地被传播下去;

Node prev(前驱节点):

前驱节点,当节点加入同步队列时被设置(尾部添加)。

Node next(后继节点):

后继节点。

Node nextWaiter(等待队列中的后继节点):

等待队列中的后继节点。如果当前节点是共享的,那么这个字段将是一个SHARED常量,也就是说节点类型(独占和共享)

和等待队列中的后继节点用同一个字段。

Thread thread(线程):

 获取同步状态的线程。

1.3 同步队列基本结构

节点是构成同步队列的基础,同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会构建成为节点

加入该队列的尾部,同步队列的基本结构图如下。

同步器包含两个节点类型的引用(在Java里面,链表通过引用实现),一个指向头节点,另外一个指向尾节点。

当一个线程成功地获取了同步状态(或锁),其他线程将无法获取到同步状态,转而被构造成为节点加入到同步队列中,

而这个加入队列的过程必须要保证线程安全,因此,同步器提供了一个基于CAS的设置节点的操作方法:

compareAndSetTail(Node expect, Node update)

传递当前线程”认为“的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建立关联。

1.4 节点加入到同步队列

 在队列尾部添加节点。

1.5 首节点的设置

同步队列遵循FIFO原则,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,

而后继节点将会在获取同步状态成功时将自己设置为首节点。

设置首节点是通过获取同步状态成功的线程完成的,由于只有一个线程够成功获取到同步状态,因此设置头节点的方式

并不需要使用CAS来保证,它只需要将首节点设置成为原首节点的后继节点并断开原首节点的next引用即可。

2、独占式同步状态获取与释放

2.1 独占式同步状态获取源码分析

此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回,否则进入等待队列,

直到获取到资源为止,且整个过程忽略中断的影响。这也正是lock()的语义,当然不仅仅只限于lock()。

获取到资源后,线程就可以去执行其临界区代码了。

public final void acquire(int arg) {
    // 尝试获取同步状态
    if (!tryAcquire(arg) &&
        // addWaiter构建节点,并加入队尾,acquireQueued根据当前节点尝试获取同步状态,
        // 获取失败,则阻塞,如果成功,则返回
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 获取锁失败,标记线程状态为中断状态
        selfInterrupt();
}

tryAcquire(int arg)

首先调用自定义同步器实现的tryAcquire(int arg)方法,该方法保证线程安全的获取同步状态,如果获取成功则返回true,

否则同步状态获取失败,返回false;

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

该方法是AQS的可重写方法,由子类去实现相应逻辑。

addWaiter(Node mode)

如果获取同步状态失败,则会调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),

首先会调用addWaiter(Node.EXCLUSIVE),构造同步节点(独占式Node.EXCLUSIVE,

同一时刻只能有一个线程成功获取同步状态)并通过addWaiter(Node mode)方法将当前线程构建的节点加入到等待队列的队尾,

并返回当前线程所在的节点。

private Node addWaiter(Node mode) {
    // 根据给定模式构造节点,mode有两种:EXCLUSIVE(独占式)和SHARED(共享式)
    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) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果快速方式添加队列失败,则通过enq(node)来完成初始化和入队操作
    enq(node);
    // 返回当前线程所在的节点
    return node;
}

enq(Node node)

private Node enq(final Node node) {
    // CAS自旋,直到成功加入队尾
    for (;;) {
        Node t = tail;
        // 队列为空,创建一个空的标志节点作为head节点,并将tail也指向它
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 正常流程,放入队尾,这个时候队列有两个元素,一个是new Node()头结点
            // 另外一个是基于获取同步状态构建的Node节点
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

同步器通过"死循环"来保证节点的正确添加,在"死循环"中只有通过CAS将节点设置成为尾节点之后,当前线程才能从该方法返回,

否则,当前线程不断尝试设置。 

acquireQueued(final Node node, int arg)

通过tryAcquire()和addWaiter(),该线程获取资源失败,已经被放入等待队列尾部。

acquireQueued()负责调用线程的暂停、在等待队列中排队获取资源,直到获取资源才返回。

l boolean acquireQueued(final Node node, int arg) {
    // 标记是否获取资源失败,默认true获取失败
    boolean failed = true;
    try {
        // 标记等待过程中是否被中断过
        boolean interrupted = false;
        // CAS自旋
        for (;;) {
            /*
             * 如果前驱是head,即当前节点已变成第二个节点,那么便有资格去尝试获取资源
             *(可能是前驱节点释放完资源唤醒自己的,当然也可能被interrupt了)。
             */
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                /*
                 * 拿到资源后,将自己设置成为head,同时断掉原head的链接p.next=null;让GC回收。
                 */
                setHead(node);
                // jvm采用可达性分析算法判定对象是否可以被回收,让对象没有引用,使jvm回收
                p.next = null; // help GC
                // 获取锁成功,failed设置为false,finally的cancelAcquire取消获取锁逻辑就不需要走
                failed = false;
                // 返回等待过程中的中断状态
                return interrupted;
            }
            // shouldParkAfterFailedAcquire修改头节点状态成功后通过 
            // parkAndCheckInterrupt方法的LockSupport.park(this)暂停线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 如果等待过程中被中断过,就将interrupted标记为true
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

shouldParkAfterFailedAcquire(Node, Node)

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.
         * 
         * 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
         * 注意:那些放弃的节点,它们相当于形成一个无引用链,在GC时会被回收。
         */
        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.
         *
         * 如果前驱正常,那就把前驱的状态设置成SIGNAL,以便唤醒后继节点。
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {
    // 暂停线程
    LockSupport.park(this);
    // 返回线程的中断状态
    return Thread.interrupted();
}

park()会让当前线程进入waiting状态。在此状态下,有两种途径可以唤醒该线程:

1)被unpark()

2)被interrupt()

小结:

acquire(int arg)调用过程

 selfInterrupt()

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

线程中断操作。

2.2 独占式同步状态释放

release(int arg)

此方法是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),

它会唤醒等待队列里的其他线程来获取资源。这也正是unlock()的语义,当然不仅仅只限于unlock()。

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()来释放资源。有一点需要注意的是,它是根据tryRelease()的返回值来判断该线程是否已经完成释

放掉资源了!所以自定义同步器在设计tryRelease()的时候要明确这一点!

tryRelease(int arg)

此方法尝试去释放指定量的资源。

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

跟tryAcquire()一样,这个方法是需要独占模式的自定义同步器去实现的。正常来说,tryRelease()都会成功的,

因为这是独占模式,该线程来释放资源,那么它肯定已经拿到独占资源了,直接减掉相应量的资源即可(state-=arg),

也不需要考虑线程安全的问题。但要注意它的返回值,上面已经提到了,release()是根据tryRelease()的返回值来判断

该线程是否已经完成释放掉资源了!所以自义定同步器在实现时,如果已经彻底释放资源(state=0),要返回true,否则返回false。

unparkSuccessor(Node 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.
         */
        // node一般为当前线程所在的节点
        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.
         */
        // 找到下一个需要唤醒的节点s
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {// 如果为空或已取消
            s = null;
            // 从这里可以看出,<=0的节点,都是还有效的节点
            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()唤醒等待队列中头节点后面的那个未放弃线程,这里我们也用s来表示吧。此时,再和acquireQueued()联系起来,

s被唤醒后,进入if (p == head && tryAcquire(arg))的判断(即使p!=head也没关系,它会再进入shouldParkAfterFailedAcquire()

寻找一个安全点。这里既然s已经是等待队列中最前边的那个未放弃线程了,那么通过shouldParkAfterFailedAcquire()的调整,

s也必然会跑到head的next节点,下一次自旋p==head就成立啦),然后s把自己设置成head标杆节点,表示自己已经获取到

资源了,acquire()也返回了!

3、共享式同步状态获取与释放

3.1 共享式同步状态获取

acquireShared(int arg)

此方法是共享模式下线程获取共享资源的顶层入口。它会获取指定量的资源,获取成功则直接返回,获取失败则进入等待队列,

直到获取到资源为止,整个过程忽略中断。

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
       doAcquireShared(arg);
}

tryAcquireShared(int arg)

protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

tryAcquireShared()尝试获取资源,成功则直接返回。

失败则通过doAcquireShared()进入等待队列,直到获取到资源为止才返回。

doAcquireShared(int arg)

此方法用于将当前线程加入等待队列尾部暂停,直到其他线程释放资源唤醒自己,自己成功拿到相应量的资源后才返回。 

private void doAcquireShared(int arg) {
    // 加入队列尾部
    final Node node = addWaiter(Node.SHARED);
    // 获取锁是否失败标识
    boolean failed = true;
    try {
        // 等待过程中是否被中断过的标志
        boolean interrupted = false;
        for (;;) {
            // 获取前驱节点
            final Node p = node.predecessor();
            // 如果到head的下一个,因为head是拿到资源的线程,此时node被唤醒,很可能是head用完资源来唤醒自己的
            if (p == head) {
                // 尝试获取资源
                int r = tryAcquireShared(arg);
                if (r >= 0) {// 成功
                    // 将head指向自己,还有剩余资源可以再唤醒之后的线程
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)// 如果等待过程中被打断过,此时将中断补上
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 判断状态,寻找安全点,进入waiting状态,等着被unpark()或interrupt()
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

setHeadAndPropagate(Node node, int propagate)

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    // head指向自己
    setHead(node);
    /*
     * Try to signal next queued node if:
     *   Propagation was indicated by caller,
     *     or was recorded (as h.waitStatus either before
     *     or after setHead) by a previous operation
     *     (note: this uses sign-check of waitStatus because
     *      PROPAGATE status may transition to SIGNAL.)
     * and
     *   The next node is waiting in shared mode,
     *     or we don't know, because it appears null
     *
     * The conservatism in both of these checks may cause
     * unnecessary wake-ups, but only when there are multiple
     * racing acquires/releases, so most need signals now or s
     * anyway.
     */
    // 如果还有剩余量,继续唤醒下一个邻居线程
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

此方法在setHead()的基础上多了一步,就是自己苏醒的同时,如果条件符合(比如还有剩余资源),还会去唤醒后继节点,

毕竟是共享模式!

doReleaseShared()

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒后继
                unparkSuccessor(h);
            }
            // head发生变化
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

3.2 共享式同步状态释放

releaseShared(int arg)

此方法是共享模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果成功释放且允许唤醒等待线程,

它会唤醒等待队列里的其他线程来获取资源。 

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

独占模式下的tryRelease()在完全释放掉资源(state=0)后,才会返回true去唤醒其他线程,这主要是基于独占下可重入的考量;

而共享模式下的releaseShared()则没有这种要求,共享模式实质就是控制一定量的线程并发执行,那么拥有资源的线程在释放掉

部分资源时就可以唤醒后继等待节点。例如,资源总量是13,A(5)和B(7)分别获取到资源并发运行,C(4)来时只剩1个

资源就需要等待。A在运行过程中释放掉2个资源量,然后tryReleaseShared(2)返回true唤醒C,C一看只有3个仍不够继续等待;

随后B又释放2个,tryReleaseShared(2)返回true唤醒C,C一看有5个够自己用了,然后C就可以跟A和B一起运行。

而ReentrantReadWriteLock读锁的tryReleaseShared()只有在完全释放掉资源(state=0)才返回true,所以自定义同步器

可以根据需要决定tryReleaseShared()的返回值。

tryReleaseShared(int arg)

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

doReleaseShared()

此方法主要用于唤醒后继。

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h); // 唤醒后继
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) 
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed(head发生变化)
            break;
    }
}

4、独占式超时获取同步状态

tryAcquireNanos(int arg, long nanosTimeout)

独占式超时获取同步状态顶层接口。

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

tryAcquire(int arg)

与正常独占式获取锁同一源码。

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

doAcquireNanos(int arg, long 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 {
        // CAS自旋
        for (;;) {
            // 获取前驱节点
            final Node p = node.predecessor();
            /*
             * 如果前驱是head,即该节点已变成第二个节点,那么便有资格去尝试获取资源
             *(可能是前驱节点释放完资源唤醒自己的,当然也可能被interrupt了)。
             */
            if (p == head && tryAcquire(arg)) {
                /*
                 * 拿到资源后,将head指向该节点。所以head所指的标杆节点,
                 * 就是当前获取到资源的那个节点或null。
                 */
                setHead(node);
                // jvm采用可达性分析算法判定对象是否可以被回收,让对象没有引用,使jvm回收
                p.next = null; // help GC
                // 返回等待过程中是否被中断过
                failed = false;
                return true;
            }
            // 计算获取锁的超时时间
            nanosTimeout = deadline - System.nanoTime();
            // 如果小于等于0,说明获取锁超时,则返回获取同步状态失败
            if (nanosTimeout <= 0L)
                return false;
            // 判断状态,寻找安全点,进入waiting状态,等着被parkNanos
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                // 线程超时停止
                LockSupport.parkNanos(this, nanosTimeout);
            // 如果线程被中断过,则抛出线程中断异常
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

参考文献

《Java并发编程的艺术》方腾飞 魏鹏 程晓明著;

Java API文档;

发布了502 篇原创文章 · 获赞 358 · 访问量 118万+

猜你喜欢

转载自blog.csdn.net/yhl_jxy/article/details/102535590