03月29, 2017

AQS基本原理

本文总结J.U.C并发组件的核心基础AQS相关原理。

####Synchronized与 Lock 控制多线程并发访问资源安全的思路一直是依靠锁的获取与释放来完成。在JDK1.5之前代码同步利用synchronized关键字通过JVM底层屏障指令来完成对象锁的操作。而随着JDK1.5 J.U.C并发包的加入,java.util.concurrent.locks.Lock;接口被加入。开发者可以通过对该接口实现类提供的lock()unlock()方法灵活的控制锁的获取与释放。

  • 获取锁方法

    /**
    * Acquires the lock.
    *
    * <p>If the lock is not available then the current thread becomes
    * disabled for thread scheduling purposes and lies dormant until the
    * lock has been acquired.
    ....
    */
    void lock();
    

    该方法从注释上看出是比较直接的获取锁方式,在当前锁被占用的情况下线程会休眠直到获取到锁( dormant until the lock has been acquired.)。

  • 尝试获取锁

  /**
     * Acquires the lock only if it is free at the time of invocation.
     *
     * <p>Acquires the lock if it is available and returns immediately
     * with the value {@code true}.
     * If the lock is not available then this method will return
     * immediately with the value {@code false}.
     *
     */
    boolean tryLock();

相对委婉的获取锁方式,获取锁的结果可立即返true or false(Acquires the lock if it is available and returns immediately)。

AQS队列同步器

java.util.concurrent.locks.Lock;接口用于为锁的使用者提供接口抽象,而对于锁的实现者而言,屏蔽同步状态的管理、线程的排队、等待、唤醒等简化锁实现的操作依赖于队列同步器提供的底层支持,也就是java.util.concurrent.locks.AbstractQueuedSynchronizer,简称AQS

AQS使用一个Deque(FIFO)的数据结构维护竞争锁的线程状态。线程的相关信息被构建为一个个Node节点。

Node:
  • volatile int waitStatus;:节点状态
  • volatile Node prev;:前继节点
  • volatile Node next;:后续节点
  • volatile Thread thread;:当期线程对象
获取锁 :
/**
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
acquire():
  • tryAcquire(arg)由AQS子类实现,尝试获取锁。
  • 在获取锁失败后,addWaiter(Node.EXCLUSIVE)将当前线程包装成Node节点(这里Node.EXCLUSIVE标识独占式节点)并加入等待队列尾部。 addWaiter()实现:
   /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 尾节点设为前置节点
        Node pred = tail;
        // 尝试快速将该节点设置为队列尾部
        if (pred != null) {
        // 将尾节点设置为当前节点的前置节点
            node.prev = pred;
            // CAS方式将当前节点设置为同步队列的尾部
            if (compareAndSetTail(pred, node)) {
            // CAS操作成功后将前置节点的后继节点引用指向当前节点
                pred.next = node;
                return node;
            }
        }
        // 快速更新失败的情况下自旋方式更新尾部节点,直至成功
        enq(node);
        return node;
    }
acquireQueued()
  /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    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);
        }
    }

最后判断当前节点的前置节点是不是队列的头节点且尝试获取锁,因为AQS规定头节点存放当前被激活(当前锁拥有者)的线程Node对象。所以在公平锁的AQS实现规则下,所有竞争者遵循Deque的FIFO出入顺序,当前锁持有者释放,只有首节点的后继节点会获取锁。

最后用一张流程图做总结: alt

本文链接:https://check321.net/post/aqs_intro.html

-- EOF --