美文网首页
JUC系列 - AQS CLH同步队列

JUC系列 - AQS CLH同步队列

作者: FX_SKY | 来源:发表于2017-03-31 13:39 被阅读360次

上一篇文章:JUC系列 - AQS简介 介绍了AQS,本篇将结合AbstractQueuedSynchronizer源码来分析。

CLH同步队列

CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),其定义如下:

static final class Node {
    /** 共享 */
    static final Node SHARED = new Node();
    /** 独占 */
    static final Node EXCLUSIVE = null;

    static final int CANCELLED =  1;
    
    static final int SIGNAL    = -1;
    
    static final int CONDITION = -2;
    
    static final int PROPAGATE = -3;

    
    volatile int waitStatus;

    /** 前驱节点 */
    volatile Node prev;

    /** 后继节点 */
    volatile Node next;

    /** 获取同步状态的线程 */
    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;
    }
}
CLH.png

入队(enqueue)

熟悉数据结构的同学,CLH队列入队是再简单不过了,无非就是tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。


    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;
    }
    /**
     * 入队(enqueue)
     */
    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;
                }
            }
        }
    }

addWaiter(Node node)先通过快速尝试设置尾节点,如果失败,则调用enq(Node node)方法设置尾节点。

enqueue.png

出队(dequeue)

CLH同步队列遵循FIFO,首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单,head执行该节点并断开原首节点的next和当前节点的prev即可,注意在这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态。

    /**
     * Sets head of queue to be node, thus dequeuing. Called only by
     * acquire methods.  Also nulls out unused fields for sake of GC
     * and to suppress unnecessary signals and traversals.
     *
     * @param node the node
     */
    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

独占式同步状态获取

acquire(int arg)方法为AQS提供的模板方法,该方法为独占式获取同步状态,但是该方法对中断不敏感,也就是说由于线程获取同步状态失败加入到CLH同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移除。代码如下:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    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);
        }
    }

相关文章

  • ReentrantLock(AQS),Volatile,Sync

    本文参考: JUC学习(八):AQS的CLH队列并发编程——详解 AQS CLH 锁JMM和底层实现原理 AQS ...

  • JUC系列 - AQS CLH同步队列

    上一篇文章:JUC系列 - AQS简介 介绍了AQS,本篇将结合AbstractQueuedSynchronize...

  • 公平锁和非公平锁

    AQS内部维护着一个FIFO队列,该队列就是CLH同步队列。CLH同步队列是一个FIFO双向队列,AQS依赖它来完...

  • Java中AQS基本实现原理

    一、AQS概述 AQS全名AbstractQueuedSynchronizer,意为抽象队列同步器,JUC(jav...

  • AQS的原理及源码分析

    AQS是什么 AQS= volatile修饰的state变量(同步状态) +FIFO队列(CLH改善版的虚拟双向队...

  • ReentrantLock实现原理及源码分析

    前面[《JUC并发核心AQS同步队列原理详解》]介绍了AQS的同步等待队列的实现原理及源码分析,这节我们将介绍一下...

  • AbstractQueuedSynchronizer

    1.概述 AbstractQueuedSynchronizer(抽象队列同步器,以下简称AQS)内部维护一个CLH...

  • Day 29 AQS

    AQS 具备特性 阻塞,等待独占,共享公平,非公平可重入允许中断. . 同步等待队列clh,双向链表构筑的队列 条...

  • java.util.concurrent解析——Abstract

    上一篇博客中,我们提到AQS的队列管理是基于CLH锁队列实现的,所以首先我们来看下CLH锁队列。 1 CLH锁队列...

  • AQS

    paper传送门 在AQS中维护着一个FIFO的同步队列,当线程获取同步状态失败后,则会加入到这个CLH同步队列的...

网友评论

      本文标题:JUC系列 - AQS CLH同步队列

      本文链接:https://www.haomeiwen.com/subject/slqvottx.html