java并发之AQS

 之前我们提到了CAS的原理和实现方式,但是CAS只能对一个变量进行原子操作,而我们实际的应用场景中的资源可不只是仅仅一个变量,还可以是资源对象。那么面对这样的场景时我们应该如何进行处理呢?
java并发大师Doug Lea早就想到了这一问题,也为这个问题提出了解决的方案,就是我们大名鼎鼎的AQS(Abstract Queued Synchronizer)
这其实是一个抽象类,即这是一个框架,里面很多具体的方法在我们需要使用的时候还需要重写。这里仅仅介绍几个类中的经典方法。
 首先介绍的是抽象类中的几个属性,其中就有Node节点,Node节点中又有自己的属性,分别是

  • volatile int waitStatus;
  • volatile Node prev;
  • volatile Node next;
  • volatile Thread thread;
static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        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;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        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;
        }
    }

AQS中的重要属性

  • private transient volatile Node head;
  • private transient volatile Node tail;
  • private volatile int state;

并且AQS中有两个非常重要的方法,分别是tryAcquireacquire;一个是尝试获取锁,即我这个线程尝试去获取资源,如果获取不到呢也没关系。acquire则是我这个线程执行一定需要这个资源,如果没有拿到这个资源我就等,直到我拿到资源为止。

protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 我们注意到,tryAquire是直接抛出了一个异常,即大师希望我们继承了类之后如果需要则自己编写实现逻辑。而acquire则是使用了final进行修饰,即逻辑流程已经固定。
 那么我们就将焦点聚集到acquire上面,我们首先从acquire的if判断的最里面开始分析,即addWaiter方法。

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;
    }

这里的逻辑其实很简单,首先我们将当前线程封装成节点,然后维护了一个双向链表模拟的队列,找到链表中的尾节点。判断它是否为空,如果不为空,我们则利用CAS将尾节点的next的指针指向当前线程。如果当前队列为空,我们则调用完整的队列建立方法来维护队列。

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应该是作者为了提高效率,提高代码的实用性。
然后我们就来到了最重要的方法:acquireQueued

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);
        }
    }

首先判断节点的前置节点是否是头节点,头结点是已经获得了资源的节点,如果是,那么就尝试去获取资源,如果失败的话则看当前节点是否需要挂起。

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.
             */
            return true;
        if (ws > 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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

这里主要是判断当前节点的前一个节点的waitStatus,如果前一个节点的状态是SIGNAL,那么我们的节点就可以挂起了,因为SIGNAL代表我们通知了前一个节点来唤醒我们,我们可以安心的挂起了。
如果前一个节点的状态值大于0,则代表节点已经被取消了,那么我们需要将它从队列中删除。否则如果前一个节点的状态值是其他情况,我们需要CAS将其设为SIGNAL,以确保我们到时候能被唤醒,然后安全挂起线程。

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

在使用完资源以后我们还需要释放资源,因此我们还需要了解relase方法是如何实现的

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方法,如果返回true,那么就会进入逻辑操作,首先找到头结点,判断其是否为空以及其状态值是否为0,然后进入unparkSuccessor

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);
    }

首先判断头结点的状态值,小于0则使用CAS将其置为0,然后找到头结点的后继节点,将其取消挂起,如果后继节点已经被取消,那么就从后往前找到最近的状态值小于0的节点,将其取消挂起。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值