并发编程—AQS源码分析

        AQS(AbstractQueuedSyncronizer)是一个抽象的队列同步器,通过维护一个共享资源状态(volatile int state)和一个先进先出(FIFO)的线程等待队列来实现一个多线程访问共享资源的同步框架。

AQS原理

        AQS为每个共享资源都设置一个共享资源锁,线程在需要访问共享资源时首先需要获取共享资源锁,如果获取到了共享资源锁,便可以在当前线程中使用该共享资源;如果获取不到,则将该线程放入线程等待队列,等待下一次资源调度。许多同步类的实现都依赖于AQS,例如常用的ReentrantLock、Semphore、CountDownLatch。

AQS共享资源的方式:独占式和共享式

        AQS定义了两种资源共享方式:独占式(Exclusive)和共享式(Share)

  • 独占式:同一时间只有一个线程可以执行,具体的Java实现有ReentrantLock
  • 共享式:同一时间多个线程可以同时执行,具体的Java实现有Semphore和CountDownLatch

         AQS只是一个框架(采用模版方法设计模式,只定义了一些抽象方法),具体资源的获取、释放都由自定义同步器去实现。不同的自定义同步器争用共享资源的方式不同,自定义同步器在实现时只需实现共享资源state的获取与释放方式即可,至于线程等待队列的维护,如获取资源失败入队、唤醒出队等,AQS已经在顶层实现好,不需要具体的同步器再做处理。

模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

自定义同步器的主要方法如表所示:

序号方法名资源共享方式说 明
1isHeldExclusively()查询该线程是否正在独占资源,只有用到Condition才需要去实现它
2tryAcquire(int arg)独占方式尝试获取资源:成功返回true,失败返回false
3tryRelease(int arg)独占方式尝试释放资源:成功返回true,失败返回false
4tryAcquireShared(int arg)共享方式尝试获取资源:负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
5tryReleaseShaed(int arg)共享方式尝试释放资源:如果释放资源后允许唤醒后续等待线程,则返回true,否则返回false

        一般来说,自定义同步器要么采用独占式,要么采用共享式,实现类只需实现tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared中的一组即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,例如ReenttrantReadWriteLock在读取时采用了共享方式,在写入时采用了独占方式。

源码分析

        AQS维护了volatile int类型的变量state,用于表示当前的同步状态。volatile虽然不能保证操作的原子性,但是能保证当前变量state的可见性。state的访问方式有三种:getState()、setState()和compareAndSetState(),均是原子操作,其中,compareAndSetState的实现依赖于Unsafe的compareAndSwaplnt() 具体的。

//将同步状态设置为给定的更新状态值(如果当前状态值等于预期值)
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

        除此之外,AQS还维护了线程等待队列(CLH队列的变体)的头节点和尾节点。该等待队列是由链表实现,并以自旋的方式获取资源,获取失败时进行阻塞的先进先出(FIFO)的双向队列。通过自旋和CAS操作保证Node节点插入和移除的原子性。当有线程获取共享资源失败,就被添加到队列末尾。

/*等待队列的头部,惰性初始化。除了初始化之外,它只能通过seHead方法进行修改。
注意:如果head存在,它的waitStatus保证不会是CANCELLED。*/
private transient volatile Node head;

/*等待队列的尾部,惰性初始化。仅通过方法enq修改以添加新的等待节点。*/
private transient volatile Node tail;
内部类Node 

        AQS同步队列中,Node节点表示一个线程,它保存着请求共享资源线程的引用(thread)、等待状态(waitStatus)、前驱节点(pre)、后继节点(next)。

static final class Node {
    //标记一个节点正在共享模式下等待
    static final Node SHARED = new Node();

    //标记一个节点正在独占模式下等待
    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;

    //当前节点状态
    volatile int waitStatus;

    //当前节点的前驱节点
    volatile Node prev;

    //当前节点的后继节点
    volatile Node next;

    //节点引用的线程
    volatile Thread thread;
}

独占模式

        acquire(int arg)是独占式获取共享资源的方法,JDK源码如下。

public final void acquire(int arg) {
    //tryAcquire方法由子类负责实现,尝试获取共享资源
    if (!tryAcquire(arg) &&
        //获取失败时,调用addWaiter方法把线程构建成Node,入队到CLH队列尾部
        //acquireQueued方法尝试获取共享资源并检查是否需要挂起线程
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

        addWaiter(Node mode)方法将当前运行的线程封装成Node节点,插入等待队列尾部,并返回其在等待队列中的节点。

private Node addWaiter(Node mode) {
    //根据传入的共享/独占模式,将线程封装成Node节点
    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;
        //CAS操作
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //自旋入队
    enq(node);
    return node;
}

        enq(final Node node)方法通过自旋方式进行CAS操作。将Node节点插入同步队列尾部使用CAS操作保证其原子性,但是其中if语句块中的代码并没有使用任何手段来保证线程安全。假如线程A执行到此处CPU分配时间耗尽,发生上下文切换,其前驱节点的next并没有指向该节点;此时其他线程释放共享资源后采用从头部到尾部的方式next遍历唤醒等待线程,就会漏掉部分节点。为了避免这种情况的发生,释放共享资源时 unparkSuccessor(Node node)方法采用从尾部到头的方式prev遍历方式。

private Node enq(final Node node) {
    //自旋多次尝试入队
    for (;;) {
        Node t = tail;
        //初始化CLH队列
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //CAS
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                //此处代码并没有使用任何手段来保证线程安全!
                //高并发情况下,假如从CLH队列头部到尾部next遍历就会漏掉部分节点
                t.next = node;
                return t;
            }
        }
    }
}

        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;
            }
            //shouldParkAfterFailedAcquire(prev, node)检查当前节点是否应该park
            //parkAndCheckInterrupt()用于park当前节点中的线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //发生了意料之外的异常,将节点移除,避免影响到其他节点
        if (failed)
            cancelAcquire(node);
    }
}

         shouldParkAfterFailedAcquire(Node pred, Node node)方法判断是否需要阻塞线程。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //前驱节点的状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         * 前驱节点状态是SIGNAL状态表示node应该被唤醒,当前node可以安全的阻塞,返回true
         */
        return true;
    if (ws > 0) {
        /*
         * 前驱节点状态>0,则为CANCELLED,表明该节点已经超时或者被中断,
         * 需要从同步队列删除该前驱节点,直到前驱节点状态小于0
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * 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.
         */
         //通过CAS的方式将前驱节点的状态设置为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

        parkAndCheckInterrupt() 方法使用LockSupport.park()方法阻塞线程并返回线程的中断状态。

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

        release(int arg)是独占模式释放共享资源的方法 ,JDK源码如下。

public final boolean release(int arg) {
    //tryRelease是AQS的模板方法,由子类实现,尝试释放共享资源
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒同步队列中的后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

        unparkSuccessor(Node node)方法唤同步队列中的线程。

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)
        compareAndSetWaitStatus(node, ws, 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.
     */
    //从后向前遍历找到距离head节点最近且没有被CANCELLED的Node节点
    //上面enq(final Node node)方法解释了为什么是从后向前遍历
    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);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值