JAVA并发编程之——AQS

概述

AQS,AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等)。

AQS内部包含一个FIFO的同步等待队列,简单的说,没有成功获锁的线程会在这个队列中等待,这个队列是一个双向链表。

在基于AQS构建的同步器中,只能在一个时刻发生阻塞,从而降低上下文切换的开销,提高了吞吐量。同时在设计AQS时充分考虑了可伸缩行,因此J.U.C中所有基于AQS构建的同步器均可以获得这个优势。

源码分析

Node内部类

节点类Node内部定义了一些常量,如节点模式、等待状态;Node内部有指向其前驱和后继节点的引用(类似双向链表);Node内部有保存当前线程的引用;Node内部的nextWaiter域在共享模式下指向一个常量SHARED,在独占模式下为null或者是一个普通的等待条件队列(只有独占模式下才存在等待条件)。

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;  
    /** 
     * 表示接下来的一个共享模式请求(acquireShared)要无条件的传递(往后继节点方向)下去 
     */  
    static final int PROPAGATE = -3;  
     /** 
     * 等待状态域, 取以下值: 
     *   SIGNAL:     当前节点的后继节点已经(或即将)被阻塞,所以如果当前节点释放(控制权)  
     *               或者被取消时,必须唤醒其后继节点。为了避免竞争,请求方法必须首先  
     *               声明它们需要一个信号,然后(原子的)调用请求方法,如果失败,当前线程 
     *               进入阻塞状态。 
     *   CANCELLED:  表示当前节点已经被取消(由于超时或中断),节点一旦进入被取消状态,就 
     *               不会再变成其他状态了。具体来说,一个被取消节点的线程永远不会再次被 
     *               阻塞 
     *   CONDITION:  表示当前节点正处在一个条件队列中。当前节点直到转移时才会被作为一个 
     *               同步队列的节点使用。转移时状态域会被设置为0。(使用0值和其他定义值  
     *               并没有关系,只是为了简化操作) 
     *   PROPAGATE:  表示一个共享的释放操作(releaseShared)应该被传递到其他节点。该状态 
     *               值在doReleaseShared过程中进行设置(仅在头节点),从而保证持续传递, 
     *               即使其他操作已经开始。 
     *   0:          None of the above 
     * 
     * 这些状态值之所以用数值来表示,目的是为了方便使用,非负的值意味着节点不需要信号(被唤醒)。 
     * 所以,一些代码中不需要针对特殊值去做检测,只需要检查符号(正负)即可。 
     *  
     * 针对普通的同步节点,这个域被初始化为0;针对条件(condition)节点,初始化为CONDITION(-2) 
     * 需要通过CAS操作来修改这个域(如果可能的话,可以使用volatile写操作)。 
     */ 
    volatile int waitStatus;
    //前节点
    volatile Node prev;
    //后节点
    volatile Node next;
    //入队列时的当前线程
    volatile Thread thread;
    //存储在condition队列中的后继节点
    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;  
    }  
}

属性

当state>0时表示锁已被占用,当state = 0时锁被释放

    //头节点
    private transient volatile Node head;

    //尾节点
    private transient volatile Node tail;

    //同步状态。
    private volatile int state;

主要API方法

AQS的功能可以分为两类:独占功能和共享功能,它的所有子类中,要么实现并使用了它独占功能的API,要么使用了共享锁的功能,而不会同时使用两套API,即便是它最有名的子类ReentrantReadWriteLock,也是通过两个内部类:读锁和写锁,分别实现的两套API来实现的。

  • tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态;
  • tryRelease(int arg):独占式释放同步状态;
  • tryAcquireShared(intarg):共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;
  • tryReleaseShared(int arg):共享式释放同步状态;
  • isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占;

独占锁——获取锁

acquire

尝试获取锁,获取不到则创建一个waiter(当前线程)后放到队列中,如果两个操作都失败则中断当前线程。

Lock中的lock()方法就是通过此方法实现的。


public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    //如果acquireQueued(addWaiter(Node.EXCLUSIVE),arg)返回true,
    //说明当前线程被中断,会继续调用selfInterrupt方法
        selfInterrupt();
}
tryAcquire

AQS中的tryAcquire方法是一个空方法(留给子类去实现),为何不是抽象方法,原因是AQS有两种功能,面向两种使用场景,需要给子类定义的方法都是抽象方法了,会导致子类无论如何都需要实现另外一种场景的抽象方法,显然,这对子类来说是不友好的。


protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

下面的tryAcquire方法是ReentrantLock中非公平锁的获取锁方法:


protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //检查锁的状态,锁未被持有
    if (c == 0) {
        // 使用CAS尝试更新状态
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //锁被持有,但当前线程就是锁的拥有者
    else if (current == getExclusiveOwnerThread()) {
     //计数递增
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
addWaiter

如果获取锁,tryAcquire返回true,反之,返回false,回到AQS的acquire方法。如果没有获取到锁,应该将当前线程放到队列中去:

private Node addWaiter(Node mode) {
    //使用当前线程构造Node,mode是表示这个节点是独占的,还是共享的
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    //将节点加入到队列尾部(这里试着快速入队,失败之后再自旋入队)
    Node pred = tail;
    //队列不为空的时候,先尝试通过cas方式修改尾节点为最新的节点
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //进入enq中死循环,“自旋”方式加入队列
    enq(node);
    return node;
}

/* 
 * <pre>
 *      +------+  prev +-----+       +-----+
 * head |      | <---- |     | <---- |     |  tail
 *      +------+       +-----+       +-----+
 * </pre>
 */
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        //第一次有线程进入同步等待队列时,要进行初始化,初始化的结果就是头尾节点都指向一个哑(dummy)节点。
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            //尝试将当前节点设置为同步等待队列的尾节点。 
            if (compareAndSetTail(t, node)) {
            //成功,将之前尾节点的后继节点指向当前节点
                t.next = node;
                return t;
            }
        }
    }
}
acquireQueued

当前线程会一直尝试获取同步状态,前提是只有其前驱节点为头结点才能够尝试获取同步状态,理由:

保持FIFO同步队列原则。
头节点释放同步状态后,将会唤醒其后继节点,后继节点被唤醒后需要检查自己是否为头节点


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)) {
             //成功后,将head节点移除,node变成头节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //检查前一个节点的状态,看当前获取锁失败的线程是否需要挂起。
            //如果需要,借助JUC包下的LockSopport类的静态方法Park挂起当前线程,直到被唤醒
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
        // 取消请求,对应到队列操作,就是将当前节点从队列中移除。
            cancelAcquire(node);
    }
}

到这里,一个线程完成了一次锁的获取,结果有两种:成功获取到锁(不用进入到AQS队列中);获取失败,被挂起,等待下次唤醒后继续循环尝试获取锁。值得注意的是,AQS的队列为FIFO队列,所以,每次被CPU假唤醒,且当前线程不是出在头节点的位置,也是会被挂起的。AQS通过这样的方式,实现了竞争的排队策略。

shouldParkAfterFailedAcquire

节点获取锁失败时,检测并更新改节点的(等待)状态。只有当前节点的前一个节点为SIGNAL时,当前节点才能被堵塞(挂起),也就是返回true。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 //获取前驱节点的等待状态。
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * 如果当前节点的前驱节点的状态为SIGNAL,表明当前节点需要被
         * park(阻塞),此时则返回true。
         */
        return true;
    if (ws > 0) {
        /*
         * 如果当前节点的前驱节点是取消状态,那么需要跳过这些(取消状态)前驱节点
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * 如果前继节点为“0”或者“共享锁”状态,则设置前继节点为SIGNAL* 状态。
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {  
    //阻塞当前线程。  
    LockSupport.park(this);  
    //线程被唤醒,方法返回当前线程的中断状态,并重置当前线程的中断状态(置为false)。  
    return Thread.interrupted();  
}  
cancelAcquire

 private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;
//将要取消的节点的thread域置空。
    node.thread = null;

    //跳过状态为"取消"的前驱节点。
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    Node predNext = pred.next;

    //当前Node节点置为取消状态
    node.waitStatus = Node.CANCELLED;

     // 如果当前节点是尾节点,那么删除当前节点
    if (node == tail && compareAndSetTail(node, pred)) {
     //将前驱节点(已经设置为尾节点)的next置空。 
        compareAndSetNext(pred, predNext, null);
    } else {
    //如果当前节点不是尾节点,则后面有其他等待线程,需要做一些唤醒工作。

        // 如果当前节点的前驱节点不是头节点,那么尝试将当前节点的前驱节点的等待状态改成SIGNAL,并尝试将前驱节点的next引用指向  其后继节点。 
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            //将Node前驱节点的next指针指向node的后继节点
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            //唤醒当前节点的后继节点。  
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

unparkSuccessor

如果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;
    //头结点head的waitStatus值为-1,则用CAS指令重置为0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);


     //获取当前节点的后继节点,如果满足状态,那么进行唤醒操作
     //如果没有满足状态,从等待队列的队尾反向查找离 当前节点最近的且状态不是"取消"的节点。   
    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);
}

独占锁—— 响应中断

acquireInterruptibly
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    //如果当前线程被中断,放弃方法执行(抛出异常)
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}
doAcquireInterruptibly

与acquireQueued方法类似,区别是对中断状态的处理,这里没有将中断状态传递给上层,而是直接抛出InterruptedException异常。


private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
    //直到成功或者当前线程被中断
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
acquireInterruptibly
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {  
    if (Thread.interrupted())  
        throw new InterruptedException();  
    //执行tryAcquire方法直到成功或者当前线程被中断或者超时时间耗尽
    return tryAcquire(arg) || 
        doAcquireNanos(arg, nanosTimeout);  
} 
doAcquireNanos
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

独占锁——释放锁

释放锁成功后要把节点移除

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
tryRelease

与tryAcquire方法一样,AQS本身没有具体的实现,

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

ReenttrantLock中的tryRelease方法,和获取锁的相应对应,获取一个锁,标示位+1,释放一个锁,标志位-1。

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    //如果释放的线程和获取锁的线程不是同一个,抛出非法监视器状态异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //因为是重入的关系,不是每次释放锁c都等于0,直到最后一次释放锁时,才通知AQS不需要再记录哪个线程正在获取锁。
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

共享锁——获取锁

共享式与独占式的最主要区别在于同一时刻独占式只能有一个线程获取同步状态,而共享式在同一时刻可以有多个线程获取同步状态。例如读操作可以有多个线程同时进行,而写操作同一时刻只能有一个线程进行写操作,其他操作都会被阻塞。

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
    //获取失败,自旋获取同步状态
        doAcquireShared(arg);
}

方法调用tryAcquireShared(int arg)方法尝试获取同步状态,如果获取失败
则调用 doAcquireShared(int arg)自旋方式获取同步状态

doAcquireShared
 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) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

共享锁——释放锁

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
doReleaseShared
 private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        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))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

参考:

http://www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer

http://brokendreams.iteye.com/blog/2250372

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值