• J.U.C之AbstractQueuedSynchronizer 抽象队列同步器


    在AQS源码中我们会看到这样一段注释:

    * The wait queue is a variant of a "CLH" (Craig, Landin, and
    * Hagersten) lock queue. CLH locks are normally used for
    * spinlocks.
    

    注释说AQS 是CLH 锁的一个变种,CLH 锁通常用于自旋锁.

    AQS 是J.U.C中用来构建锁和其他同步组件的基础框架类,在java se5之前,我们主要依靠synchronized关键字实现锁功能,在se5之后加入了更多类型的同步组件,这些组件虽然失去了像synchronize关键字隐式加锁解锁的便捷性,但是却拥有了锁获取和释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性,需要注意的是synchronized同步块执行完成或者遇到异常是锁会自动释放,而同步组件必须调用类似unlock()方法释放锁,因此在finally块中释放锁

    Node{
        static final Node SHARED = new Node();//共享模式下的等待
        static final Node EXCLUSIVE = null;   //独占模式下的等待
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;//等待状态的线程引用
        Node nextWaiter;
    }
    

    Node是构建AQS的基础,通过Node的数据结构来看AQS中包含一包含前驱和后继,并且引用当前处于等待的线程的一个双向链表,是一个FIFO队列。而AQS就是通过头尾节点指针来管理这个双向链表,同时实现在获取锁失败时的线程入链尾,在释放锁时对同步队列中的线程进行通知等核心动作。锁的获取和释放其实就对应着节点的入队和出队操作。其中waitStatus状态值如下:

    属性 备注
    waitStatus 1. CANCELLED 值为1 indicate thread has cancelled 表示线程已被取消
    2. SIGNAL 值为-1 indicate successor's thread needs unparking 后继节点包含的线程需要运行,
    也就是unpark
    3. CONDITION 值为-2 indicate thread is waiting on condition 当前节点在等待condition,
    也就是在condition队列中
    4. PROPAGATE 值为-3 the next acquireShared should unconditionally propagate
    后续的acquireShared 能够得以执行
    5. 0 当前节点在sync队列中,等待着获取锁
    nextWaiter 存储condition队列中的后继节点
    SHARED/EXCLUSIVE 用于标记一个节点在共享模式下等待/独占模式下等待

    接下来看AQS独占锁时的获取和释放源码:

    独占锁时获取锁:

    /**
     * 尝试获取独占锁,不响应中断.  首先通过tryAcquire尝试获取1次锁,如果成功则返回,否则,
     * 将线程包装成node入队列
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    

    acquire方法流程

    首先通过子类判断是否获取了锁,如果获取了就什么也不干。

    如果没有获取锁、通过线程创建节点加入同步队列的队尾。

    当线程在同步队列中不断的通过自旋去获取同步状态,如果获取了锁,就把其设为同步队列中的头节点,否则在同步队列中不停的自旋等待获取同步状态。

    如果在获取同步状态的过程中被中断过最后自行调用interrupted方法进行中断操作。

    /**
     * 在队列中获取此节点的锁,不响应中断. 
     * 在队列中会不断检测是否为head的直接后继,并尝试获取锁,
     * 如果获取失败,则会通过LockSupport阻塞当前线程,直至被释放锁的线程唤醒或者被中断,随后再次尝试获取锁,如此反复.
     */
    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 final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    
    /**
     * 检测更新获锁失败的节点的状态,如果需要阻塞,则返回true.
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)//当前驱节点释放后,这个节点就会去尝试获取状态
            /*
             * 前驱节点设置为SIGNAL状态,在释放锁的时候会唤醒后继节点,
             * 所以后继节点(也就是当前节点)现在可以阻塞自己.
             */
            return true;
        if (ws > 0) {//前驱节点被取消
            /*
             * 循环尝试找到没有被取消的前驱作为当前node的前驱
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 等待状态为0或者PROPAGATE(-3),设置前驱的等待状态为SIGNAL,
             * 并且之后会回到循环再次重试获取锁.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    
    /**
     * 唤醒后继节点.
     */
    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;//获取头节点的状态
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);//通过CAS将头节点的状态设置为初始状态
    
        /*
         * 通常要唤醒的线程所在节点为当前节点的直接后继,如果后继被取消或为null
         * 则会反向从尾部节点回溯到第一个没有被cancel的节点作为唤醒线程所在节点
         */
        Node s = node.next;
        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);//唤醒
    }
    
    /**
     * 新增1个给定模式的节点.
     */
    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;
    }
    
         /**
         * 通过循环+CAS在队列中成功插入一个节点后返回
         */
        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;
                    }
                }
            }
        }
    

    独占锁时释放锁:

    首先子类自定义的方法如果释放了同步状态,如果头节点不为空并且头节点的等待状态不为0就唤醒其后继节点。主要依赖的就是子类自定义实现的释放操作。

    /**
     * 排他锁释放. 
     * This method can be used to implement method {@link Lock#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;
    }
    

    AQS共享锁获取和释放:

    共享模式下获取锁

    /**
     * 获取共享锁,不响应中断. 尝试获取1次共享锁,如果成功则返回.
     * 否则包装成节点入队列, 然后不断的阻塞循环尝试获取锁,直到tryAccquireShared获取成功.
     */
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0) //交给子类实现
            doAcquireShared(arg);
    }
    
    /**
     * 获取共享锁.将共享节点加入队列Acquires in shared uninterruptible mode.
     */
    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) {
                    //一旦共享获取成功,设置新的头结点,并且唤醒后继线程
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {//大于0代表获取到了
                        setHeadAndPropagate(node, r);//设置为头节点并且如果有多余资源一并唤醒
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //判断线程是否可以进行休息如果可以休息就调用park方法
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    /**
     * 设置队列头节点, and checks if successor may be waiting
     * in shared mode, if so propagating if either propagate > 0 or
     * PROPAGATE status was set.
     */
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = 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 soon
         * anyway.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||//大于0代表还有其他资源一并可以唤醒
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }
    
    /**
     * 共享模式下释放锁
     */
    private void doReleaseShared() {
        /*
         * 此外,我们必须循环以防新节点加入
         * 同样不像其他地方使用
         * unparkSuccessor的方式,我们需要先知道是否cas操作失败,如是,则重新检测
         */
        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))//-3
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
    

    共享模式下释放锁

    /**
     * 共享模式释放锁. 
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    
  • 相关阅读:
    人肉搜索利大于弊?
    上海美容美发连锁店疯狂扩展,大面值会员卡慎办
    jQuery简单入门
    把苦难装在心里《赢在中国》(20080527)
    赢在中国(080304)
    ASP.NET MVC Preview 2新特性
    Lucifer的一场暴强围英雄表演
    php pack、unpack、ord 函数使用方法(二进制流接口应用实例)
    PHP开发绝对不能违背的安全铁则(摘)
    MySQL数据类型:UNSIGNED注意事项
  • 原文地址:https://www.cnblogs.com/oxf5deb3/p/13676243.html
Copyright © 2020-2023  润新知