一、简介

AbstractQueuedSynchronizer简称AQS,从名字理解,它是一个抽象队列同步锁,是重入锁、读写锁的基类。如果我们要全面学习java的并发锁,首先要掌握AQS的加锁机制。下面文章我将介绍AQS的结构,加锁和解锁方法。

二、什么是AQS

1.AQS初认识

AQS的加锁原理可以用下面的图表示,每次调用加锁时,会对int类型的成员变量state进行判断。

加锁:当state不等于0时,会把当前节点加到链表中,阻塞等待,直到被前驱唤醒,head指向自己时才会去争取锁。争取锁的时候是用了unsafe.compareAndSwapInt方法,其实AQS里面使用了大量Unsafe里的方法,它是不安全的,我们一般很少直接调用,但是人家Doug Lea大神却使用得游刃有余。

当state等于0,并且调用compareAndSwapInt方法成功时,表示获得了锁。

解锁:获得锁的线程在调用解锁方法后,会把head指向到下一个节点,并且唤醒下一个节点去竞争获取锁。

Java系统开发设计如何增量同步订单数据 java增量包_等待队列

2.AQS类关系图

从关系图中可以看到AQS继承了AbstractOwnableSynchronizer,与AbstractQueuedLongSynchronizer并列关系。

AbstractOwnableSynchronizer主要有两个方法,用户保存和获取占用锁的线程。

AbstractQueuedLongSynchronizer里面的方法实现基本与AbstractQueuedSynchronzier相同,名字的差别在于Long,其实也就是他的state成员变量是Long类型。它作用表现在可以保存更多的状态位,在锁重入、读写锁的数量方面有更多的选择。估计很少场景需要用到它。

Java系统开发设计如何增量同步订单数据 java增量包_成员变量_02

3.基本数据结构

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

	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;
        /** 代表线程在另外一个队列或者链表中等待,可以把condition理解成第二等待队列,只有从第二队列移到第一队列才能参与锁的竞争 */
        static final int CONDITION = -2;
        /** 传播状态,指示后继节点可以无条参与读锁的竞争 */
        static final int PROPAGATE = -3;

        /** 等待状态,为以上几种类型 */
        volatile int waitStatus;

        /** 上一个节点 */
        volatile Node prev;

        /** 下一个节点 */
        volatile Node next;

        /** 当前线程 */
        volatile Thread thread;

        /** 在conditin等待队列中,指向下一个等待节点  */
        Node nextWaiter;
    }

	// 可以理解为它就是第二等待队列
	public class ConditionObject implements Condition, java.io.Serializable {
        /** 指向第一个等待节点 */
        private transient Node firstWaiter;
        /** 指向最后一个等待节点. */
        private transient Node lastWaiter;
	}
}

三、AQS的使用

1.加锁

// 一般加锁会首先调用这个方法,参数为加锁的计数器,累加到成员变量state
	public final void acquire(int arg) {
		// 如果不能立刻,则加入到队列中去获取
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
	 }

	// 子类实现,一般是state成员变量加上arg
	protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
	 }

	// 这里把一个节点加到队列末端
	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;
}

	// 入队列
	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;
                }
            }
        }
    }

	// 等待队列的节点获取到锁会返回true,否则阻塞等待
	final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                // 如果上一个节点是头节点,说明可以参与锁竞争了。并且锁竞争成功则当前节点占用锁,把head指向自己
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
				
				// 竞争锁失败,判断是否阻塞线程,下面分析这个方法
                if (shouldParkAfterFailedAcquire(p, node) &&
                	// 这里是阻塞线程,用的是LockSupport.park(this)
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
            	// 失败则说明是有异常了,则取消该线程的等待。后面分析这方面
                cancelAcquire(node);
        }
    }

	// 竞争锁失败时,判断线程是否需要阻塞
	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            // 前驱状态时signal,则需要阻塞等待唤醒
            return true;
        if (ws > 0) {
            // 前驱状态大于0,说明是取消的状态,则向前遍历找到不大于0的前驱
            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.
             */
			// 到这一步说明前驱的状态要么是0,要么是PROPAGATE(传播状态),则当前线程可以再次参与锁竞争一次,如果第二次还没有获取到,则前驱状态变为sigal了会到第一步阻塞线程
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

	// 取消锁竞争
	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;

        // 前驱的下一个节点,
        Node predNext = pred.next;

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

        // 如果当前节点是尾节点,则设置前驱为尾节点
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            // 如果前驱不为头节点,并且(前驱状态是sigal,或者状态小于0并且设置状态sigal成功),并且前驱线程不为null,则设置前驱的下一个节点为当前节点的下一个节点
            int ws;
            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 {
            	// 到这步说明需要参与锁竞争了
                unparkSuccessor(node);
            }

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

2.解锁

// 解锁相对简单
	public final boolean release(int arg) {
		// 这里一般是状态state减去arg
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	// 唤醒后继节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

四、总结

AQS是重入锁和同步队列的基础类,是学习JUC的第一课。以上是我对AQS的了解,如有不对之处欢迎指正。