队列同步器AQS源码学习


一、什么是AQS
1、概念

       AQS全称AbstractQueueSynchronizer,抽象队列同步器,是Doug Lea大师创作的用来构建锁或者其他同步组件(信号量、事件等)的基础框架类。常见的重入锁、读写锁、循环栅栏、倒计数器、信号量等同步器的底层都是依赖它实现。这个抽象类完成了同步器底层基础细节实现。
       如下类图参考自 zhangt85的博客:javaJDK并发包类图
在这里插入图片描述

2、通过重入锁简单了解AQS

       直观的看重入锁ReentrantLock的lock操作,调用了内部类Sync的lock方法。

	private final Sync sync;
	
	public void lock() {
        sync.lock();
    }

       Sync是ReentrantLock一个内部的抽象类,继承自AbstractQueuedSynchronizer,lock是它的抽象方法。

	abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();
        //省略部分代码
		// ......
    }

       具体来看Sync 有公平锁FairSync和非公平锁NonfairSync 两种子类,公平锁的lock直接调用了acquire(1),非公平锁通过CAS更新了某内部状态,成功则设置资源独占线程,失败则调用acquire(1)。
       aquire方法通过源码跟踪可以找到是AQS的方法。也就是说锁的实现其实根本还是AQS。其实类似的也可以看看unlock调用的是AQS的release方法。
       在重入锁的情况下,同时占有锁资源的线程肯定只有一个,属于独占模式,而AQS还有一种共享模式,可多个线程同时占有资源,后文也会分析。

	// 非公平锁
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
        	// 通过CAS立即更新AQS为锁定状态,成功则设置资源独占线程为当前线程,失败则通过AQS的acquire方法参与资源竞争。
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        //省略部分代码
		// ......
    }
	// 公平锁
	static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
        //省略部分代码
		// ......
    }
	public void unlock() {
        sync.release(1);
    }
二、AQS的实现

       这一节将会从原理和源码层次对AQS进行分析。

1. AbstractQueueSynchronizer继承自AbstractOwnableSynchronizer

       AbstractOwnableSynchronizer比较简单,只有exclusiveOwnerThread这一个属性以及其getter、setter方法。这个属性的主要作用是放置独占式资源竞争模式下占有资源的线程。

	public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer{......}
2. AQS的主要属性及内部Node类
1) 重要属性

       AQS内部维护着线程阻塞队列和资源状态,具体有以下几个属性:

属性名类型说明
headNode内部CLH队列的头结点
tailNode内部CLH队列的尾结点
stateint同步状态值
2) Node类

       Node类是CLH队列的节点,节点有一个属性为waitStatus,表示节点状态,它可能值为以下几个:

状态数值说明
CANCELLED1由于同步队列中等待的线程超时或者被中断,需要从同步队列中取消等待,节点进入该状态后将不会变化。
SIGNAL-1后继线程处于等待状态。而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点得以运行。
CONDITION-2节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()方法之后,该节点会从等待队列转移到同步队列中,加入到对同步状态的获取中。
PROPAGATE-3表示下一次共享式同步状态获取将会无条件被传播下去。
初始态0初始状态,基本可视为Node对象new出来的默认状态

       waitStatus大于0的情况只有一种:被取消的CANCELLED。
       Node类属性:

属性名类型说明
waitStatusint表示节点的状态,可能值如上表
prevNode前驱节点,当节点加入同步队列被设置(尾部添加)
nextNode后继节点
nextWaiterNode等待队列中的后继节点。如果当前节点是共享的,那么这个字段将是一个SHARED常量,也就是说节点类型(独占和共享)和等待队列中的后继节点共用同一个字段
threadThread获取同步状态的线程
3. 线程阻塞队列

       AQS线程阻塞队列的实现是基于CLH队列锁的变种,CLH锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。类似的还有MCS队列

1) 基本结构

       同步队列的基本结构如下图,队首是一个哨兵节点,pre为空,next指向下一个节点,后面的节点pre指向前一个节点,next指向下一个节点。同步器中head属性存放队首节点,tail属性存放尾结点。队首节点是一个哨兵节点,负责连接next队列元素,没有thread内容。
       每一个节点有一个线程,资源获取成功的线程在释放资源的时候会唤醒哨兵节点的下一个节点,并把这个节点设置为哨兵节点。资源未获取成功的线程则封装为新的节点加入队尾然后阻塞线程。
       需要注意这是一个先入先出的队列,不支持线程优先级。
同步队列的基本结构

2) 确保同步器的队列更新以及state更新的线程安全

       同步器自然是要和多个线程打交道。在java的多线程编程中,更新某一对象的时候确保线程安全我们通常使用synchronized关键字,这个是jvm层面提供的同步机制。但此处的AQS是一个同步器的底层实现,使用synchronized关键字性能会大打折扣。
       AQS保证线程安全使用的是Unsafe类提供的CAS(compare and set)操作,在更新对象的某一个属性的时候,会比较该属性当前值是否是预期值,是则更新并返回成功,不是预期值则表明该属性已被修改,数据版本不一致,将会放弃更新并返回失败。CAS操作是由更底层机制(如cpu指令集)保证的原子操作
       compareAndSwapObject的第一个参数是要修改的对象,第二个参数是属性值的偏移量,第三个参数是当前属性值的期望值,第四个参数是将要更新的值。这个了解即可。

	static {
        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }

	/**
     * CAS head field. Used only by enq.
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    /**
     * CAS tail field. Used only by enq.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    /**
     * CAS waitStatus field of a node.
     */
    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

    /**
     * CAS next field of a node.
     */
    private static final boolean compareAndSetNext(Node node,
                                                   Node expect,
                                                   Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }
4. 独占模式
1)AQS资源获取入口acquire和资源释放入口release

       从源码来看,这里用了设计模式中的模板方法模式,子类继承并实现tryAcquiretryRelease,但父类的acquire调用流程不变。
资源获取
       这个方法是资源获取的入口,我们可以继续往下跟各个方法。

    public final void acquire(int arg) {
    	// 尝试获取资源,失败则加入等待队列并开始排队,如果有中断则设置中断标志。
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    static void selfInterrupt() {
        Thread.currentThread().interrupt(); // Just to set the interrupt flag
    }

资源释放
       调用了尝试释放资源的tryRelease方法,释放成功则唤醒下一个线程。继续往下跟。

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

尝试获取资源
       tryAcquire是需要用户自己继承重写的方法,默认会抛不支持这个操作的异常。

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

       可以看看重入锁的tryAcquire实现,在重入锁的设定中,state为0表示资源可获取,即可获取锁,为1则表示资源被其他线程占用。我们分析代码可知,tryAcquire的作用是请求并更新资源状态,成功则返回true,失败返回false。我们实现自己的同步器的时候可参照jdk实现的同步器代码实现自己的功能。

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

	final boolean nonfairTryAcquire(int acquires) {
	    final Thread current = Thread.currentThread();
	    // 获取AQS的state属性的值
	    int c = getState();
	    if (c == 0) {
	    	// c == 0表示锁资源可获取,通过CAS操作更新state
	        if (compareAndSetState(0, acquires)) {
	        	// 成功则顺利获取资源,把当前线程设为资源独占线程 exclusiveOwnerThread
	            setExclusiveOwnerThread(current);
	            return true;
	        }
	     }
	     else if (current == getExclusiveOwnerThread()) {
	     	// c不为0,但资源已被当前线程占用,则更新state,增加相应的资源请求值。(这是重入锁可重入的重要实现)
	         int nextc = c + acquires;
	         if (nextc < 0) // overflow
	             throw new Error("Maximum lock count exceeded");
	         // 注意:这里没有使用CAS更新状态值,因为资源只被当前线程持有,能直接操作state的只有当前线程,不存在线程安全问题
	         setState(nextc);
	         return true;
	     }
	     // 资源获取失败都返回false
	     return false;
	}

尝试释放资源
       tryRelease和tryAcquire类似,都是需要用户自己继承覆盖的资源释放方法。

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

       我们同样可以看看重入锁的实现,因为独占模式对资源状态的更改只会有一个活动线程,因此无需CAS保证线程安全。

        protected final boolean tryRelease(int releases) {
        	// 释放后资源状态值
            int c = getState() - releases;
            // 独占模式,释放的时候持有线程和独占线程不一致就是异常现象。
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 如果C为0,则表示当前线程持有的锁全部释放完(重入锁可多次加锁),则把独占线程清掉
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            // 更新资源状态值
            setState(c);
            return free;
        }

公平锁的尝试获取
       公平锁的实现主要是在获取资源的时候判断头结点后面是否有排队,如果有则强制获取失败并把把当前线程节点排到队尾以实现公平的目的。

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            	// 这里确认有没有线程排队
                if (!hasQueuedPredecessors() &&
                    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;
        }
        
	// 头结点后面是否有非本线程的节点线程排队
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
3)入队操作 addWaiter

       如果资源被占用,则tryAcquire会失败,将会通过addWaiter方法向等待队列添加节点:

	// 参数mode代表独占模式还是共享模式
    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;
        // pred不为空,即当前tail不为空,表明前面的队列已经有节点,只需要把当前节点插入到队尾。 
        // node.pred->tail, tail.next->node, tail->node
        if (pred != null) {
            node.prev = pred;
            // 对tail的更新操作依然要使用CAS保证线程安全
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 1、如果pred为空,即尾结点为空,代表队列前面没有节点,当前节点为队列的第一个节点
        // 2、tail的CAS更新操作可能会因为有多个线程共同操作tail而失败
        // 这两种情况都会进入enq方法,这是一个通用的入队操作(失败自旋直到成功)。
        enq(node);
        return node;
    }

    private Node enq(final Node node) {
    	// 死循环,退出的条件只有通过CAS将tail属性更新成功。
        for (;;) {
            Node t = tail;
            // tail为空,代表当前入队节点为整个队列的第一个节点
            if (t == null) { 
            	// 需要new一个哨兵节点作为头结点,此时队列中只有这一节点
            	// 此时当前传入的node并未入队,将会在下一个循环开始入队
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	// 这个分支已经确保队尾tail不会为空
                node.prev = t;
                // 保证线程安全的更新队尾: node.pred->tail, tail.next->node, tail->node
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    // 只有更新成功才回退出这个循环。返回当前这个节点。
                    return t;
                }
            }
        }
    }
4)排队等待获取资源 acquireQueued

       在完成入队操作之后,该线程应该排队等待前面的节点都获取并释放资源,然后该线程才能拿到资源。在通常的CLH队列中,每个节点的线程应该不断轮询前一个节点的状态,若前一个节点释放了资源,本节点的线程才能获取到资源往下走。
       显然,很多没有意义的空轮训会极大增加CPU的占用率,导致程序性能直线下降。因此,AQS对CLH队列进行了改进,不进行没有意义的空轮训,没有获取到资源的时候满足休眠条件会将本线程休眠。
       需要注意这里判断获取到资源的条件,位于CLH队列除头结点以外的第一个节点,并且进行资源竞争的时候成功。我们知道每个线程执行acquire去竞争资源的时候都会先调用一下tryAcquire尝试竞争获取资源,可能后面刚开始进行资源竞争的线程成功而此处失败,因此这里不能保证每个线程按照队列顺序获取到资源。也就是说虽然CLH队列是一个FIFO的队列,但并不能保证公平

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	// 获取前一个节点,如果为空会抛出 NullPointerException
                final Node p = node.predecessor();
                // 如果前一个节点是头结点并且通过tryAcquire获取到了资源,则表示本节点不用再继续在队列中等待
                // 注意:因为acquire的实现,这里不能保证公平
                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 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);
        }
    }
   

       加入了等待时间的资源获取竞争的代码也十分类似,只多了关于等待时间的运算以及线程休眠的时间。可自行阅读分析一下。

    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);
        }
    }
5)竞争失败休眠的条件 shouldParkAfterFailedAcquire

       在上面排队等待的过程中,会判断线程是否应该进入休眠,这一节就来具体看看什么情况下线程会进入休眠。
       在执行这个方法的时候,当前线程必然是尝试获取资源失败了。根据源码,判断当前线程是否应休眠是依据前一个节点的状态来确定的。当前一个节点为:
       SIGNAL:表明前一个节点释放了资源会唤醒下一个线程,即会唤醒本线程,因此可以放心休眠本线程。
       CANCELLED:大于0的状态值只有CANCELLED(=1),表示前一个节点被取消了。这里就需要将被取消的节点移除出队列。源码中的实现是依次向前找到所有连续的被取消的节点移除出队列。也就是找到最近的前一个没有取消入队的节点作为前驱节点。
       其他情况:包括CONDITION、PROPAGATE还有0,这些或是默认值或是其他某些特殊情形下的状态,都把他们设定为SIGNAL,这样本线程休眠之后,前驱节点释放了资源后会唤醒这个线程。
       前驱节点状态在除SIGNAL以外的情况,shouldParkAfterFailedAcquire方法都对队列有过修改,都返回false等待下次循环再进行判断。

	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		// 前一个节点的状态
        int ws = pred.waitStatus;
        // 当前一个节点为SIGNAL,则可休眠,返回true
        if (ws == Node.SIGNAL)
            return true;
        // 当前一个节点状态值大于0,表示被取消的节点,则依次向前找到所有被取消的节点,移除出等待队列。
        // 线程安全:
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        	// 其他的所有情况都把前驱节点的状态值设为 SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        // 在判断过程中改变了队列的分支都返回false,等待下一个循环进来判断。
        return false;
    }
6)休眠和检查中断 parkAndCheckInterrupt

       线程休眠操作比较简单,直接用LockSupport的park方法阻塞当前线程,参数为负责此线程阻塞的对象。调用这个方法后线程恢复运行可能是以下情况:
       ① 其他线程调用以当前线程为目标的unpark方法。
       ② 其他线程向当前线程传入中断信息。
       ③ 虚假调用,没有缘由直接返回。(这个一般不考虑)
       此方法直接返回了当前线程是否中断,因为中断信号也会使当前线程继续开始运行。

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    
	// LockSupport的park方法
	public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        // 这个park方法就是native方法了
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
7)取消竞争 cancelAcquire

       在竞争资源期间发生了异常就会取消该线程的资源竞争。取消资源竞争的主要操作就是把当前节点以及前面连续相邻的所有被取消的节点移除出等待队列。
       根据往前依次遍历到的第一个没有被取消竞争的pred节点和当前节点node的情况,有几种处理方式:
       ① 当前节点是尾结点,则把pred后面的所有节点删除 。
       ② 如果pred的继任者需要被唤醒,则设置pred的next为node的next节点,等价于删掉被取消的这些节点。
       ③ pred继任者无法被唤醒,都应该直接唤醒node下一个节点的线程。

    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;

        // 找到前面没有被取消的节点,这个节点可能就是本节点的前驱节点,也可能是头结点,也可能是头结点到前驱节点之间的任意节点
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // 记录下没有被取消的节点的下一个节点,后面CAS更新节点的时候会用到
        Node predNext = pred.next;

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        node.waitStatus = Node.CANCELLED;

        // 当前节点是尾结点,则把pred后面的所有节点删除 。
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            // 如果pred的继任者需要被唤醒,则设置pred的next为node的next节点,等价于删掉被取消的这些节点。
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
            	// pred继任者无法被唤醒,直接唤醒node下一个节点的线程。
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }
8)唤醒继任者 unparkSuccessor

       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;
        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.
         */
        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);
    }
5. 共享模式

共享模式和独占模式比较接近,所以就简略分析。

1)获取资源acquireShared和释放资源releaseShared

       与独占模式类似,都是调用自定义的尝试获取资源,更新资源状态,如果失败则进入入队、排队操作。
       需要注意,tryAcquireShared返回值为负数表示竞争失败,为正数和0表示竞争成功。

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

       释放资源也是调用尝试释放资源,更新资源状态,然后进入释放流程。

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
2)尝试获取资源acquireShared和尝试释放资源releaseShared

       和独占模式一样,尝试获取共享资源和尝试释放资源也需要用户继承覆盖,这里给一个Semaphore不公平模式下的实现,可自行分析一下。共享模式的公平锁的思想和独占模式一致,就不再赘述。
尝试获取资源

    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
		protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

尝试释放资源

    protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }
        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }
3)执行共享模式的资源获取后操作doAcquireShared和资源释放后操作doReleaseShared

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);
        }
    }
    // 设置头结点并且如果下一个节点具备唤醒条件则唤醒
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        // 下一个线程唤醒需要的条件
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

doReleaseShared
       执行资源释放后的操作,依据头结点状态来唤醒下一个线程。

    private void doReleaseShared() {
        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;
        }
    }
参考资料

       《Java并发编程的艺术》
       javaJDK并发包类图


AQS中还有ConditionObject,后面再分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值