zoukankan      html  css  js  c++  java
  • AQS框架

    和Synchronized相比,可重入锁ReentrantLock的实现原理有什么不同?

    锁的实现原理基本是为了达到一个目的:让所有的线程都能看见某种标记
    Synchronized是在对象头中设置标记实现这一目的,是一种JVM原生锁的实现.
    ReentrantLock和其他所有的基于lock接口实现的类,都是通过一个volitile修饰的int型变量,并保证每个线程都能拥有对该int的可见性和原子修改。其本质是基于AQS框架实现的。

    什么是AQS框架?

    AQS(AbstractQueueSynchronizer类)是一个用来构建锁和同步器的框架,各种Lock包中的锁,甚至早起的FutureTask,都是基于AQS构建的。

    1.AQS在背部定义了一个volatile int state变量,表示同步状态:当线程调用Lock方法时,如果state=0,说明没有任何线程占用共享资源锁,可以获得锁并将状态state改为1,;如果state=1,说明有线程正在使用共享变量,其他线程必须加入同步队列并等待。

         /**
         * The synchronization state.
         */
        private volatile int state;
    

    2.AQS通过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 {
        ……
        }
    
    • Node类是对要访问同步代码线程的封装,包含线程本身及其状态waitStatus(有五种取值:是否被阻塞,是否等待唤醒,是否已经被取消等),每个Node节点关联其prev节点和next节点,方便线程释放锁后幻想下一个等待的线程,是一个FIFO的过程。
            /**
             * 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;
    
    • Node类有两个常量,SHARED和EXCLUSIVE,分别代表共享模式和独占模式。所谓共享模式就是一个锁熏晕多个线程同事操作(信号量Semaphore就是AQS的共享模式实现的),独占模式是同一个时间段只能有一个线程对共享资源进项操作,多余的线程需要排队等待(如ReentranLock)。
            /** 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;
    

    3.AQS通过内部类ConditionObject构建等待队列(可有多个),当Condition调用wait()后,线程将会加入等待队列中,Condition调用signal()后,线程会从等待队列中转移到同步队列中进行锁竞争。

    4.AQS和Condition各自维护不同的队列,在使用Lock和Condition的时候,其实就是两个队列在互相移动。

    Synchronized和ReentrantLock的异同

    ReentrantLock是Lock的实现类,是一个互斥的同步锁。

    功能角度讲:ReentrantLock比synchronized更精细,可以实现synchronized实现不了的功能:

    • 等待可中断:当长期持有锁的线程不释放锁时,正在等待的线程可以选择放弃等待,对处理执行时间非常长的同步块很有用。
    • 带超时的获取锁尝试:在指定时间范围内获取锁,如果到了时间仍无法获取就返回。
    • 可以判断是否有线程在等待获取锁。
    • 可以响应中断请求:与synchronized不同,当获取到锁的线程被中断时,能够响应中断,中断异常会被抛出,同时锁会被释放。
    • 可以实现公平锁

    释放锁的角度讲:synchronized在JVM层面实现的,不但可以通过监控工具监测synchronized的锁定,代码异常时会自动释放锁;Lock加锁后必须手动释放锁。

    性能角度讲:java6改进synchronized后,在竞争不激烈的情况下,synchronized性能高于ReentrantLock;高竞争情况下,synchronized性能会下降几十倍,ReentrantLock性能会维持。

  • 相关阅读:
    ExcelManager基于.Net的Excel读写管理类库(二)
    ExcelManager基于.Net的Excel读写管理类库(一)
    NHibernate初探!
    也谈软件工程!
    本人初学WCF ,请教一个问题
    初来咋到,今天终于在这里建起了一个家!
    c#中委托和直接函数调用用什么区别,好处和目的在哪?
    CreateThread, AfxBeginThread,_beginthread, _beginthreadex的区别
    C++线程同步
    C++中using的作用
  • 原文地址:https://www.cnblogs.com/farmersun/p/12683252.html
Copyright © 2011-2022 走看看