• 并发包锁实现的精髓----队列同步器(AbstractQueuedSynchronizer)


    队列同步器

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

    同步器的主要使用方式就是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在实现抽象方法的过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的3个方法(getstate(),setState(int newState),compareAndSetState(int expect,int update))进行操作,因为它们能够保证状态的更改是线程安全的。子类推荐被定义为同步器组件的静态内部类

    队列同步器与锁之间的关系

    同步器是实现锁的关键,在锁的实现中,聚合同步器利用同步器实现锁的语义。可以这样理解锁与同步器二者之间的关系:
    1、锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节

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

    队列同步器API

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

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

    1、getState() 获取当前同步状态。
    2、setState() 设置当前同步状态。
    3、compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性。

    同步器可重写的方法:

    方法名称方法描述
    protected boolean tryAcquire(int arg)独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态
    protected boolean tryRelease(int arg)独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状
    protected boolean tryAcquireShared(int arg)共享式获取同步状态,返回值大于等于0的值,表示获取成功;反之,获取失败
    protected boolean tryAcquireRelease(int arg)共享式释放同步状态

    实现自定义同步组件时,将会调用同步器提供的模板方法,这些(部分)模板方法与描述如下:

    方法名称描述
    void acquire(int arg)独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回;否则,将会进入同步队列等待。该方法将会调用重写的tryAcquire()
    void acquireInterruptibly(int arg)与独占式相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中;如果当前线程中断,则该方法会抛出InterruptedException并返回
    boolean tryAcquireNanos(int arg, long nanosTimeout)在acquireInterruptibly()的基础上增加了超时限制,当前线程获取到同步状态则返回true;如果没有获取到,则返回false
    void acquireShared(int arg)共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是同一时刻可以有多个线程获取到同步状态
    void acquireSharedInterruptibly(int arg)与acquireShared(int arg)相同,该方法有响应中断
    boolean tryAcquireSharedNanos(int arg, long nanosTimeout)在acquireSharedInterruptibly(int arg)的基础上增加了超时限制
    boolean release(int arg)独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒
    boolean releaseShared(int arg)共享式的释放同步状态

    同步器提供的模板方法基本上分为3类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程的情况。

    同步队列

    同步器依赖内部的同步队列(一个FIFO的双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程;当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

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

    属性类型与名称描述
    int waitStatus等待状态。
    Node pre前驱节点,当节点加入同步队列时被设置
    Node next后继节点
    Node nextWaiter等待队列中的后继节点。如果当前节点是共享的,那么这个字段将是一个SHARED常量,也就是说节点类型(独占和共享)和等待队列中的后继节点共用同一个字段
    Thread thread获取同步状态的线程
    static final class Node {
    /**
             * Status field, taking on only the values:
             * 当前节点的后继节点是阻塞的,所以当当前节点释放同步状态或者取消的时候,
             * 就要unPark它的后继节点,为了避免竞争,获取同步状态的方法必须标明
             *   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 only using
             * CAS.
             */
            static final int CANCELLED =  1;
            static final int SIGNAL    = -1;
            static final int CONDITION = -2;
            static final int PROPAGATE = -3; 
            
            volatile int waitStatus;
            
             static final Node SHARED = new Node();
             
            static final Node EXCLUSIVE = null;
            
            volatile Node prev;
            
            volatile Thread thread;
            
            Node nextWaiter;
            
            final boolean isShared() {
                return nextWaiter == SHARED;
            }
            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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    深入源码,理解同步器

    acquire(int arg)
     public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    首先调用自定义同步器实现的tryAcquire(int arg)方法,该方法能够保证线程安全的获取到同步状态

    /**
             * Fair version of tryAcquire.  Don't grant access unless
             * recursive call or no waiters or is first.
             */
            protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (isFirst(current) &&
                        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;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    该方法是ReentryLock里面FairSync内部类对tryAcquire()方法重写的实现

    如果获取同步状态失败,则构造同步节点(独占式Node.EXCLUSIVE,同一时刻只能有一个线程成功获取到同步状态)并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部。

    /**
         * Creates and enqueues node for given thread and mode.
         *
         * @param current the thread
         * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
         * @return the new node
         */
        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)) {
                    pred.next = node;
                    return node;
                }
            }
            enq(node);
            return node;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    /**
         * Inserts node into queue, initializing if necessary. See picture above.
         * @param node the node to insert
         * @return node's predecessor
         */
        private Node enq(final Node node) {
            for (;;) {
                Node t = tail;
                if (t == null) { // Must initialize
                    Node h = new Node(); // Dummy header
                    h.next = node;
                    node.prev = h;
                    if (compareAndSetHead(h)) {
                        tail = node;
                        return h;
                    }
                }
                else {
                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    最后调用acquireQueued(Node node,int arg)方法,使得该节点以“死循环”的方式获取同步状态。如果获取不到,则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱结点的出队或阻塞线程被中断来实现的。

     final boolean acquireQueued(final Node node, int arg) {
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        return interrupted;
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } catch (RuntimeException ex) {
                cancelAcquire(node);
                throw ex;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    docker基础命令快速入门
    PHP知识大全
    JS 模块化 - 02 Common JS 模块化规范
    .NET Core中一些优秀的项目和框架
    内存监控以及优化
    立创EDA——PCB的布局(四)
    模拟实现【二叉搜索树】
    你知道图论的spfa吗?
    Python入门教程 | Python3 网络编程
    aigc使用意愿调查
  • 原文地址:https://blog.csdn.net/u011557841/article/details/95937785