• 并发编程(五)AQS


    1.CLH队列锁

    CLH队列锁即Craig, Landin, and Hagersten (CLH) locks。

    CLH队列锁本身也是一种基于链表的可扩展,高性能,公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。

    当一个线程需要获取锁时,会创建一个新的QNode,将其中的locked设置为true表示需要获取锁,然后线程对tail域调用getAndSet方法,使自己成为队列的尾部,同时获取一个指向其前趋的引用myPred,然后该线程就在前趋结点的locked字段上旋转,直到前趋结点释放锁。

    当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前趋结点。

    线程通过CLH获取及释放锁流程

    1)线程A需要获取锁

    创建新的QNode

     

    2)这时线程B也想要获取锁(同A一样操作)

    线程A、B都在它们自己的myPred域上自旋,一旦它的myPred节点的locked字段变为false,该线程就能获取到锁

    3)当A的前置节点将locked域设置为false,同时回收前驱节点,进行了锁的释放,线程A就可以获取到锁。

    优缺点:

    CLH队列锁的优点是空间复杂度低(如果有n个线程,L个锁,每个线程每次只获取一个锁,那么需要的存储空间是O(L+n),n个线程有n个myNode,L个锁有L个tail),CLH的一种变体被应用在了JAVA并发框架中。

    唯一的缺点是在NUMA系统结构下性能很差,在这种系统结构下,每个线程有自己的内存,如果前趋结点的内存位置比较远,自旋判断前趋结点的locked域,性能将大打折扣,但是在SMP系统结构下该法还是非常有效的。

    一种解决NUMA系统结构的思路是MCS队列锁。

    (SMP,NUMA体系结构介绍可参考:https://www.cnblogs.com/yubo/archive/2010/04/23/1718810.html)

    2.AQS(AbstractQueuedSynchronizer)

     队列同步器AbstractQueuedSynchronizer,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

    2.1 AQS使用方式和其中的设计模式

    AQS的主要使用方式是继承,子类通过继承AQS并实现它的抽象方法来管理同步状态,在AQS里由一个int型的state来代表这个状态,在抽象方法的实现过程中免不了对同步状态的更改,同步器提供了3个方法来进行状态操作,他们能够保证状态的改变是安全的。

    在实现上,ASQ的子类 推荐 被定义为 自定义同步组件的静态内部类,AQS自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式的获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。

     同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步。

    同步器和锁的关系:

    锁是面向使用者的,它定义了使用者和锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节。(产品展示,已经造出的轮子,可供程序开发使用)

    同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒底层操作。锁和同步器很好的隔离了使用者和实现者所需关注的领域。

    实现者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。

    同步器的设计基于模板方法模式。

    AQS中的方法

    实现自定义同步组件时,将会调用同步器提供的模板方法。

    
    
    /**
    * Acquires in exclusive mode, ignoring interrupts. Implemented
    * by invoking at least once {@link #tryAcquire},
    * returning on success. Otherwise the thread is queued, possibly
    * repeatedly blocking and unblocking, invoking {@link
    * #tryAcquire} until success. This method can be used
    * to implement method {@link Lock#lock}.
    *
    * @param arg the acquire argument. This value is conveyed to
    * {@link #tryAcquire} but is otherwise uninterpreted and
    * can represent anything you like.
    */独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法放回,否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg)方法
    public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();
    }

    /**
    * Acquires in exclusive mode, aborting if interrupted.
    * Implemented by first checking interrupt status, then invoking
    * at least once {@link #tryAcquire}, returning on
    * success. Otherwise the thread is queued, possibly repeatedly
    * blocking and unblocking, invoking {@link #tryAcquire}
    * until success or the thread is interrupted. This method can be
    * used to implement method {@link Lock#lockInterruptibly}.
    *
    * @param arg the acquire argument. This value is conveyed to
    * {@link #tryAcquire} but is otherwise uninterpreted and
    * can represent anything you like.
    * @throws InterruptedException if the current thread is interrupted
    */与acquire(int arg)相同,该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException并返回
    public final void acquireInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
    throw new InterruptedException();
    if (!tryAcquire(arg))
    doAcquireInterruptibly(arg);
    }

    /**
    * Attempts to acquire in exclusive mode, aborting if interrupted,
    * and failing if the given timeout elapses. Implemented by first
    * checking interrupt status, then invoking at least once {@link
    * #tryAcquire}, returning on success. Otherwise, the thread is
    * queued, possibly repeatedly blocking and unblocking, invoking
    * {@link #tryAcquire} until success or the thread is interrupted
    * or the timeout elapses. This method can be used to implement
    * method {@link Lock#tryLock(long, TimeUnit)}.
    *
    * @param arg the acquire argument. This value is conveyed to
    * {@link #tryAcquire} but is otherwise uninterpreted and
    * can represent anything you like.
    * @param nanosTimeout the maximum number of nanoseconds to wait
    * @return {@code true} if acquired; {@code false} if timed out
    * @throws InterruptedException if the current thread is interrupted
    */在acquireInterruptibly(int arg)基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回false,如果获取到返回true
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
    throws InterruptedException {
    if (Thread.interrupted())
    throw new InterruptedException();
    return tryAcquire(arg) ||
    doAcquireNanos(arg, nanosTimeout);
    }



    /**
    * Releases in exclusive mode. Implemented by unblocking one or * more threads if {@link #tryRelease} returns true. * This method can be used to implement method {@link Lock#unlock}. * * @param arg the release argument. This value is conveyed to * {@link #tryRelease} but is otherwise uninterpreted and * can represent anything you like. * @return the value returned from {@link #tryRelease} */独占式的释放同步锁,会在释放同步状态的同时,将同步队列中第一个节点包含的线程唤醒 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
    -----------------------------独占式↑------------------------------------------ -----------------------------共享式↓------------------------------------------
    /** * Acquires in shared mode, ignoring interrupts. Implemented by * first invoking at least once {@link #tryAcquireShared}, * returning on success. Otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@link * #tryAcquireShared} until success. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquireShared} but is otherwise uninterpreted * and can represent anything you like. */共享式获取同步状态,如果当前线程未获得同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程共同获取到同步状态 public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } /** * Acquires in shared mode, aborting if interrupted. Implemented * by first checking interrupt status, then invoking at least once * {@link #tryAcquireShared}, returning on success. Otherwise the * thread is queued, possibly repeatedly blocking and unblocking, * invoking {@link #tryAcquireShared} until success or the thread * is interrupted. * @param arg the acquire argument. * This value is conveyed to {@link #tryAcquireShared} but is * otherwise uninterpreted and can represent anything * you like. * @throws InterruptedException if the current thread is interrupted */与acquireShared(int arg)方法相同,该方法响应中断 public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } /** * Attempts to acquire in shared mode, aborting if interrupted, and * failing if the given timeout elapses. Implemented by first * checking interrupt status, then invoking at least once {@link * #tryAcquireShared}, returning on success. Otherwise, the * thread is queued, possibly repeatedly blocking and unblocking, * invoking {@link #tryAcquireShared} until success or the thread * is interrupted or the timeout elapses. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquireShared} but is otherwise uninterpreted * and can represent anything you like. * @param nanosTimeout the maximum number of nanoseconds to wait * @return {@code true} if acquired; {@code false} if timed out * @throws InterruptedException if the current thread is interrupted */在acquireShareInterruptibly(int arg)的基础上增加了超时限制 public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout); } /** * Releases in shared mode. Implemented by unblocking one or more * threads if {@link #tryReleaseShared} returns true. * * @param arg the release argument. This value is conveyed to * {@link #tryReleaseShared} but is otherwise uninterpreted * and can represent anything you like. * @return the value returned from {@link #tryReleaseShared} */共享式的释放同步状态 public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
    -------------------------查询同步队列中的等待线程情况--------------------------------
    /**
    * Returns a collection containing threads that may be waiting to
    * acquire. Because the actual set of threads may change
    * dynamically while constructing this result, the returned
    * collection is only a best-effort estimate. The elements of the
    * returned collection are in no particular order. This method is
    * designed to facilitate construction of subclasses that provide
    * more extensive monitoring facilities.
    *
    * @return the collection of threads
    */获取等待在同步队列上的线程集合
    public final Collection<Thread> getQueuedThreads() {
    ArrayList<Thread> list = new ArrayList<Thread>();
    for (Node p = tail; p != null; p = p.prev) {
    Thread t = p.thread;
    if (t != null)
    list.add(t);
    }
    return list;
    }

    这些模板方法分为三类:独占式获取与释放同步状态,共享式获取与释放同步状态,和查询同步队列中的等待线程。

    可重新的方法:

    // Main exported methods
    
        /**
         * Attempts to acquire in exclusive mode. This method should query
         * if the state of the object permits it to be acquired in the
         * exclusive mode, and if so to acquire it.
         *
         * <p>This method is always invoked by the thread performing
         * acquire.  If this method reports failure, the acquire method
         * may queue the thread, if it is not already queued, until it is
         * signalled by a release from some other thread. This can be used
         * to implement method {@link Lock#tryLock()}.
         *
         * <p>The default
         * implementation throws {@link UnsupportedOperationException}.
         *
         * @param arg the acquire argument. This value is always the one
         *        passed to an acquire method, or is the value saved on entry
         *        to a condition wait.  The value is otherwise uninterpreted
         *        and can represent anything you like.
         * @return {@code true} if successful. Upon success, this object has
         *         been acquired.
         * @throws IllegalMonitorStateException if acquiring would place this
         *         synchronizer in an illegal state. This exception must be
         *         thrown in a consistent fashion for synchronization to work
         *         correctly.
         * @throws UnsupportedOperationException if exclusive mode is not supported
         */独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后在进行CAS设置同步状态
        protected boolean tryAcquire(int arg) {
            throw new UnsupportedOperationException();
        }
    
        /**
         * Attempts to set the state to reflect a release in exclusive
         * mode.
         *
         * <p>This method is always invoked by the thread performing release.
         *
         * <p>The default implementation throws
         * {@link UnsupportedOperationException}.
         *
         * @param arg the release argument. This value is always the one
         *        passed to a release method, or the current state value upon
         *        entry to a condition wait.  The value is otherwise
         *        uninterpreted and can represent anything you like.
         * @return {@code true} if this object is now in a fully released
         *         state, so that any waiting threads may attempt to acquire;
         *         and {@code false} otherwise.
         * @throws IllegalMonitorStateException if releasing would place this
         *         synchronizer in an illegal state. This exception must be
         *         thrown in a consistent fashion for synchronization to work
         *         correctly.
         * @throws UnsupportedOperationException if exclusive mode is not supported
         */独占式释放同步状态,等待获取同步状态的线程将会有机会获取同步状态
        protected boolean tryRelease(int arg) {
            throw new UnsupportedOperationException();
        }
    
        /**
         * Attempts to acquire in shared mode. This method should query if
         * the state of the object permits it to be acquired in the shared
         * mode, and if so to acquire it.
         *
         * <p>This method is always invoked by the thread performing
         * acquire.  If this method reports failure, the acquire method
         * may queue the thread, if it is not already queued, until it is
         * signalled by a release from some other thread.
         *
         * <p>The default implementation throws {@link
         * UnsupportedOperationException}.
         *
         * @param arg the acquire argument. This value is always the one
         *        passed to an acquire method, or is the value saved on entry
         *        to a condition wait.  The value is otherwise uninterpreted
         *        and can represent anything you like.
         * @return a negative value on failure; zero if acquisition in shared
         *         mode succeeded but no subsequent shared-mode acquire can
         *         succeed; and a positive value if acquisition in shared
         *         mode succeeded and subsequent shared-mode acquires might
         *         also succeed, in which case a subsequent waiting thread
         *         must check availability. (Support for three different
         *         return values enables this method to be used in contexts
         *         where acquires only sometimes act exclusively.)  Upon
         *         success, this object has been acquired.
         * @throws IllegalMonitorStateException if acquiring would place this
         *         synchronizer in an illegal state. This exception must be
         *         thrown in a consistent fashion for synchronization to work
         *         correctly.
         * @throws UnsupportedOperationException if shared mode is not supported
         */共享式获取同步状态,返回大于等于0的值,表示获取成功,反之,获取失败
        protected int tryAcquireShared(int arg) {
            throw new UnsupportedOperationException();
        }
    
        /**
         * Attempts to set the state to reflect a release in shared mode.
         *
         * <p>This method is always invoked by the thread performing release.
         *
         * <p>The default implementation throws
         * {@link UnsupportedOperationException}.
         *
         * @param arg the release argument. This value is always the one
         *        passed to a release method, or the current state value upon
         *        entry to a condition wait.  The value is otherwise
         *        uninterpreted and can represent anything you like.
         * @return {@code true} if this release of shared mode may permit a
         *         waiting acquire (shared or exclusive) to succeed; and
         *         {@code false} otherwise
         * @throws IllegalMonitorStateException if releasing would place this
         *         synchronizer in an illegal state. This exception must be
         *         thrown in a consistent fashion for synchronization to work
         *         correctly.
         * @throws UnsupportedOperationException if shared mode is not supported
         */共享式释同步状态
        protected boolean tryReleaseShared(int arg) {
            throw new UnsupportedOperationException();
        }
    
        /**
         * Returns {@code true} if synchronization is held exclusively with
         * respect to the current (calling) thread.  This method is invoked
         * upon each call to a non-waiting {@link ConditionObject} method.
         * (Waiting methods instead invoke {@link #release}.)
         *
         * <p>The default implementation throws {@link
         * UnsupportedOperationException}. This method is invoked
         * internally only within {@link ConditionObject} methods, so need
         * not be defined if conditions are not used.
         *
         * @return {@code true} if synchronization is held exclusively;
         *         {@code false} otherwise
         * @throws UnsupportedOperationException if conditions are not supported
         */当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占
        protected boolean isHeldExclusively() {
            throw new UnsupportedOperationException();
        }

    访问或修改同步状态的方法

     重写同步器指定方法时,需要使用同步器提供的如下3个方法来访问或修改同步状态。

    getState():获取当前同步状态

    setState(int newState):设置当前同步状态

    compareAndState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性

    2.2 AQS中的数据结构-节点和同步队列

    节点Node:

    AQS是CLH队列锁的一种变体实现,作为队列来说,必然要有一个节点的数据结构来保存我们前面所说的各种域,如前驱节点,后继节点,节点状态等,这个数据结构就是AQS中的内部类Node。节点设计时应当考虑的问题:

    1)线程信息。要知道是作用的线程的信息

    2)队列中的线程状态。线程当前处在上面状态,是已经取消了“获锁”请求,还是在“等待中”,或者说“即将得到锁”

    3)前驱节点(线程),后继节点(线程)的信息。面线程释放锁后,当前线程才能去拿到锁;当前线程释放锁后,通知后继线程去取锁;

    Node类实际设计如下:

    /**
         * Wait queue node class.
         *
         * <p>The wait queue is a variant of a "CLH" (Craig, Landin, and
         * Hagersten) lock queue. CLH locks are normally used for
         * spinlocks.  We instead use them for blocking synchronizers, but
         * use the same basic tactic of holding some of the control
         * information about a thread in the predecessor of its node.  A
         * "status" field in each node keeps track of whether a thread
         * should block.  A node is signalled when its predecessor
         * releases.  Each node of the queue otherwise serves as a
         * specific-notification-style monitor holding a single waiting
         * thread. The status field does NOT control whether threads are
         * granted locks etc though.  A thread may try to acquire if it is
         * first in the queue. But being first does not guarantee success;
         * it only gives the right to contend.  So the currently released
         * contender thread may need to rewait.
         *
         * <p>To enqueue into a CLH lock, you atomically splice it in as new
         * tail. To dequeue, you just set the head field.
         * <pre>
         *      +------+  prev +-----+       +-----+
         * head |      | <---- |     | <---- |     |  tail
         *      +------+       +-----+       +-----+
         * </pre>
         *
         * <p>Insertion into a CLH queue requires only a single atomic
         * operation on "tail", so there is a simple atomic point of
         * demarcation from unqueued to queued. Similarly, dequeuing
         * involves only updating the "head". However, it takes a bit
         * more work for nodes to determine who their successors are,
         * in part to deal with possible cancellation due to timeouts
         * and interrupts.
         *
         * <p>The "prev" links (not used in original CLH locks), are mainly
         * needed to handle cancellation. If a node is cancelled, its
         * successor is (normally) relinked to a non-cancelled
         * predecessor. For explanation of similar mechanics in the case
         * of spin locks, see the papers by Scott and Scherer at
         * http://www.cs.rochester.edu/u/scott/synchronization/
         *
         * <p>We also use "next" links to implement blocking mechanics.
         * The thread id for each node is kept in its own node, so a
         * predecessor signals the next node to wake up by traversing
         * next link to determine which thread it is.  Determination of
         * successor must avoid races with newly queued nodes to set
         * the "next" fields of their predecessors.  This is solved
         * when necessary by checking backwards from the atomically
         * updated "tail" when a node's successor appears to be null.
         * (Or, said differently, the next-links are an optimization
         * so that we don't usually need a backward scan.)
         *
         * <p>Cancellation introduces some conservatism to the basic
         * algorithms.  Since we must poll for cancellation of other
         * nodes, we can miss noticing whether a cancelled node is
         * ahead or behind us. This is dealt with by always unparking
         * successors upon cancellation, allowing them to stabilize on
         * a new predecessor, unless we can identify an uncancelled
         * predecessor who will carry this responsibility.
         *
         * <p>CLH queues need a dummy header node to get started. But
         * we don't create them on construction, because it would be wasted
         * effort if there is never contention. Instead, the node
         * is constructed and head and tail pointers are set upon first
         * contention.
         *
         * <p>Threads waiting on Conditions use the same nodes, but
         * use an additional link. Conditions only need to link nodes
         * in simple (non-concurrent) linked queues because they are
         * only accessed when exclusively held.  Upon await, a node is
         * inserted into a condition queue.  Upon signal, the node is
         * transferred to the main queue.  A special value of status
         * field is used to mark which queue a node is on.
         *
         * <p>Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill
         * Scherer and Michael Scott, along with members of JSR-166
         * expert group, for helpful ideas, discussions, and critiques
         * on the design of this class.
         */
        static final class Node {
         //线程的两种等待模式
    /** Marker to indicate a node is waiting in shared mode */表示线程以共享式等待锁(如ReadLock) static final Node SHARED = new Node(); /** Marker to indicate a node is waiting in exclusive mode */表示线程以互斥的模式等待锁(如ReentrantLock),互斥就是一把只能由一个线程持有,不能同时存在多个线程使用同一个锁 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 //初始化Node时,默认为0 * * 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. */表示该节点所代表(对应)的线程.当拥有锁的线程释放的时候,可能会调用LockSupport.unpark(thread),唤醒这个被阻塞的线程 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. */如果是SHARED,表示当前节点是共享模式,如果是null,当前节点是独占模式,如果是其他值,当前节点也是独占模式,但是这个值也是Condition队列的下一个节点 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; } }

     补充:之前说过的Condition 是为了实现线程之间相互等待,通知的模式,Condition对象值能在独占锁中才能使用。

    当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点。

    /**
         * Head of the wait queue, lazily initialized.  Except for
         * initialization, it is modified only via method setHead.  Note:
         * If head exists, its waitStatus is guaranteed not to be
         * CANCELLED.
         */
        private transient volatile Node head;
    
        /**
         * Tail of the wait queue, lazily initialized.  Modified only via
         * method enq to add new wait node.
         */
        private transient volatile Node tail;

    注意:因为首节点head是不保存线程信息的节点,仅仅是因为数据结构设计上的需要,在数据结构上,这种做法往往叫做“空头节点链表”。对应的就有“非空头结点链表”

    节点加入到同步队列:

    首节点的变化:

     

    独占式锁的获取与释放:

    获取锁步骤:
    public
    final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }


    protected boolean tryAcquire(int arg) {//需要自己实现
    throw new UnsupportedOperationException();
    }
    
    
    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) {
    node.prev = pred;
    if (compareAndSetTail(pred, node)) {//快速设置一次 成功就直接返回 不成功就使用enq(node)循环设置到队列尾部
    pred.next = node;
    return node;
    }
    }
    enq(node);
    return node;
    }

    private Node enq(final Node node) {
    for (;;) {
    Node t = tail;
    if (t == null) { // Must initialize 必须初始化
    if (compareAndSetHead(new Node()))
    tail = head;
    } else {
    node.prev = t;
    if (compareAndSetTail(t, node)) {
    t.next = node;
    return t;
    }
    }
    }
    }
    
    
    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);
    }
    }


    private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
    }
    
    
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//获取同步状态失败后检查是否需要进行阻塞
    int ws = pred.waitStatus;//拿到前驱节点的状态
    if (ws == Node.SIGNAL)//如果前驱节点为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.
    */
    do {//从同步队列中移除前驱节点
    node.prev = pred = pred.prev;
    } while (pred.waitStatus > 0);
    pred.next = node;
    } else {//如果前驱节点状态不为SINGAL,CANCELLED 则通过CAS将其前驱节点设置为SINGAL,返回false
    /*
    * 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.
    */
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
    }
     
    private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
    }
     
    释放锁步骤:
    public
    final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }

    protected boolean tryRelease(int arg) {//自己实现(拿到锁的线程进行释放)
    throw new UnsupportedOperationException();
    }

    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;
    if (ws < 0)//如果头节点状态小于0
    compareAndSetWaitStatus(node, ws, 0);//CAS将头结点状态设置为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.
    */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {//后继节点如果为cancel 则从尾部找节点进行唤醒
    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);
    }
    唤醒后再次到
    final boolean acquireQueued(final Node node, int arg) 中循环判断拿锁,失败的话继续阻塞等待!(此处如果拿锁失败说明 有其他线程拿到了锁,其他线程拿到锁再次释放时,将有对应的节点线程被唤醒 再次去拿锁);
    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);
    }
    }
    
    

    Condition分析:

    一个Condition包含一个等待队列

    同步队列和等待队列关系

    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)) {//放入队列,进入阻塞状态
                    LockSupport.park(this);
                    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                        break;
                }
                if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//被唤醒后重新加入到AQS同步队列去尝试获取锁
                    interruptMode = REINTERRUPT;
                if (node.nextWaiter != null) // clean up if cancelled
                    unlinkCancelledWaiters();
                if (interruptMode != 0)
                    reportInterruptAfterWait(interruptMode);
            }


    private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
    unlinkCancelledWaiters();
    t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
    firstWaiter = node;
    else
    t.nextWaiter = node;
    lastWaiter = node;
    return node;
    }

    final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
    return false;
    if (node.next != null) // If has successor, it must be on queue
    return true;
    /*
    * node.prev can be non-null, but not yet on queue because
    * the CAS to place it on queue can fail. So we have to
    * traverse from tail to make sure it actually made it. It
    * will always be near the tail in calls to this method, and
    * unless the CAS failed (which is unlikely), it will be
    * there, so we hardly ever traverse much.
    */
    return findNodeFromTail(node);
    }
    
    
    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);
    }
    }
     

    signal()

     public final void signal() {
                if (!isHeldExclusively())
                    throw new IllegalMonitorStateException();
                Node first = firstWaiter;
                if (first != null)
                    doSignal(first);
            }

    private void doSignal(Node first) {
    do {
    if ( (firstWaiter = first.nextWaiter) == null)
    lastWaiter = null;
    first.nextWaiter = null;
    } while (!transferForSignal(first) &&
    (first = firstWaiter) != null);
    }

    final boolean transferForSignal(Node node) {
    /*
    * If cannot change waitStatus, the node has been cancelled.
    */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
    return false;

    /*
    * 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;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
    LockSupport.unpark(node.thread);
    return true;
    }
     

    唤醒一般使用signal(),唤醒指定等待队列(第一个节点)即可。 signalAll(),唤醒对应等待队列上的所有节点,并重新挂到同步队列尾部。

    ReenterLock中 公平与非公平锁的设计:

    ReentrantLock的构造函数中,默认的无参构造函数将会把Sync对象创建为NonfairSync对象,这是一个“非公平锁”;而另一个构造函数ReentrantLock(boolean fair)传入参数为true时将会把Sync对象创建为“公平锁”FairSync。

    nonfairTryAcquire(int acquires)方法,对于非公平锁,只要CAS设置同步状态成功,则表示当前线程获取了锁,而公平锁则不同。tryAcquire方法,该方法与nonfairTryAcquire(int acquires)比较,唯一不同的位置为判断条件多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。

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


    //公平尝试获取锁
    protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    if (!hasQueuedPredecessors() &&
    compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(current);
    return true;
    }
    }
    else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0)
    throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
    }
    return false;
    }
    
    
    public final boolean hasQueuedPredecessors() {//公平获取锁时,会判断当前节点是否有头尾,节点,以及当前节点是否为头结点的后继节点,有其他节点在等待的话就将该节点(线程)放到同步队列尾部,等待取锁
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
    ((s = h.next) == null || s.thread != Thread.currentThread());
    }
     

    ReentrantReadWriteLock 的实现:

    读锁和写锁共用了同一个静态内部类Sync,   将同步状态state通过位运算 高16位表示读锁,低16位表示写锁。

    假设当前同步状态值为S,写状态等于S&0x0000FFFF(将高16位全部抹去),读状态等于S>>>16(无符号补0右移16位)。当写状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16),也就是S+0x00010000。根据状态的划分能得出一个推论:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取。

    protected ReadLock(ReentrantReadWriteLock lock) {
                sync = lock.sync;
            }


    protected WriteLock(ReentrantReadWriteLock lock) {
    sync = lock.sync;
    }


    abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 6317671515068378041L;

    /*
    * Read vs write count extraction constants and functions.
    * Lock state is logically divided into two unsigned shorts:
    * The lower one representing the exclusive (writer) lock hold count,
    * and the upper the shared (reader) hold count.
    */

    static final int SHARED_SHIFT = 16;
    static final int SHARED_UNIT = (1 << SHARED_SHIFT);
    static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

    /** Returns the number of shared holds represented in count */
    static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
    /** Returns the number of exclusive holds represented in count */
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }



    使用ThreadLocal---HoldCounter记录每个线程进入锁的次数(读写锁中可重入的实现)
    /**
    * A counter for per-thread read hold counts.
    * Maintained as a ThreadLocal; cached in cachedHoldCounter
    */
    static final class HoldCounter {
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    final long tid = getThreadId(Thread.currentThread());
    }
    
    
    /**
    * ThreadLocal subclass. Easiest to explicitly define for sake
    * of deserialization mechanics.
    */
    static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
    return new HoldCounter();
    }
    }
     

    写锁的获取与释放

    写锁是一个支持重进入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。

    该方法除了重入条件(当前线程为获取了写锁的线程)之外,增加了一个读锁是否存在的判断。如果存在读锁,则写锁不能被获取,原因在于:读写锁要确保写锁的操作对读锁可见,如果允许读锁在已被获取的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当前写线程的操作。因此,只有等待其他读线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其他读写线程的后续访问均被阻塞。

    写锁的释放与ReentrantLock的释放过程基本类似,每次释放均减少写状态,当写状态为0时表示写锁已被释放,从而等待的读写线程能够继续访问读写锁,同时前次写线程的修改对后续读写线程可见。

    读锁的获取与释放

    读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问(或者写状态为0)时,读锁总会被成功地获取,而所做的也只是(线程安全的)增加读状态。如果当前线程已经获取了读锁,则增加读状态。

    如果当前线程在获取读锁时,写锁已被其他线程获取,则进入等待状态。读状态是所有线程获取读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由线程自身维护。在tryAcquireShared(int unused)方法中,如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态。如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全,依靠CAS保证)增加读状态,成功获取读锁。读锁的每次释放(线程安全的,可能有多个读线程同时释放读锁)均减少读状态。

    锁的升降级

    锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。

    锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

    RentrantReadWriteLock不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程)。目的是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了数据,则其更新对其他获取到读锁的线程是不可见的。

    参考:http://enjoy.ke.qq.com

  • 相关阅读:
    107. 二叉树的层次遍历 II
    106. 从中序与后序遍历序列构造二叉树
    105. 从前序与中序遍历序列构造二叉树
    【Python基础编程029 ● 判断语句和循环语句 ● while循环嵌套 】
    【Python基础编程028 ● 判断语句和循环语句 ● continue的用法 】
    【Python基础编程027 ● 判断语句和循环语句 ● break的用法 】
    【Python基础编程026 ● 判断语句和循环语句 ● 使用while语句求累加和 】
    【Python基础编程025 ● 判断语句和循环语句 ● while循环语句 】
    【Python基础编程024 ● 判断语句和循环语句 ● 使用if语句实现三目运算符】
    登录页面二次跳转的案例
  • 原文地址:https://www.cnblogs.com/cangshublogs/p/10775274.html
Copyright © 2020-2023  润新知