java并发系列之———AQS详解

一,前言

AQS( AbstractQueuedSynchronizer 抽象队列同步器 ) 是java juc并发工具类的基础,本文主要将基于ReentrantLock可重入锁的源码结合讲述AQS如何管理线程之间的协作

二,AQS基础
1,AQS基本的数据结构是FIFO双向队列,如下图:
在这里插入图片描述
(1)head tail

 /**
    * 头结点,懒加载,该结点只能被setHead方法修改。并且结点的waitStatus不能为CANCELLED
     */
    private transient volatile Node head;

    /**
      * 尾结点,懒加载,只有在enq方法中会加
     */
    private transient volatile Node tail;

(2) node状态

  /** waitStatus表示该线程已经被删除状态 */
        static final int CANCELLED =  1;
        /** waitStatus表示该节点的前节点的线程需要被挂起 */
        static final int SIGNAL    = -1;
        /** waitStatus表示该线程等待 */
        static final int CONDITION = -2;
        /**waitStatus指示下一次acquireShared方法应该无条件传播*/
        static final int PROPAGATE = -3;

2,AQS中维持了一个单一的volatile修饰的状态信息state(AQS通过Unsafe的相关方法,以原子性的方式由线程去获取这个state)。AQS提供了getState()、setState()、compareAndSetState()函数修改值(实际上调用的是unsafe的compareAndSwapInt方法)

 /**
     * The synchronization state.
     */
    private volatile int state;

    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }
    
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

3,AQS代码结构
AQS使用模板模式,提供一些方法用于给继承该类的子类重写
独占式获取
accquire
acquireInterruptibly
tryAcquireNanos
共享式获取
acquireShared
acquireSharedInterruptibly
tryAcquireSharedNanos
独占式释放锁
release
共享式释放锁
releaseShared
需要子类覆盖的流程方法
独占式获取 tryAcquire
独占式释放 tryRelease
共享式获取 tryAcquireShared
共享式释放 tryReleaseShared
这个同步器是否处于独占模式 isHeldExclusively

三,ReentrantLock介绍

ReentrantLock可重入锁,实现Lock接口,代码结构
有三个内部静态类

abstract static class Sync extends AbstractQueuedSynchronizer{}//抽象类 
static final class NonfairSync extends Sync{}//非公平锁继承Sync类
static final class FairSync extends Sync{}//公平锁继承Sync类

在这里插入图片描述

一般使用方法:

Lock lock = new ReentrantLock();

可重入锁分为非公平锁和公平锁两种,默认非公平锁

  public ReentrantLock() {
        sync = new NonfairSync();
    }
 public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

对于 ReentrantLock重写了独占式获取tryAcquire
接下来就ReentrantLock的读取锁过程对AQS源码进行讲解,以公平锁为例子。

三,公平锁加锁

final void lock() {
            acquire(1);
        }

1,acquire

acquire方法调用的是AQS类中的acquire方法

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            
            selfInterrupt();
    }
  1. 先尝试调用tryAcquire方法获取锁
  2. 获取不到,把线程打包成节点,放入队列尾部

整个流程如下图,我们按照此图来讲解
在这里插入图片描述

2,tryAcquire

tryAcquire方法源码如下

  protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();//获取同步状态,如果为0表示没有线程占用
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    //hasQueuedPredecessors方法判断是否有队列为空或者当前线程位于首节点,如果是(取反),
                    //则使用cas设置state值为acquire =1,同时设置当前线程为占用该AQS的线程
                    return true;
                }
            }
          //如果不为0,说明有线程占用了,判断占用线程是否为当前线程
            else if (current == getExclusiveOwnerThread()) {
            //线程重入,只需要在原来基础的state加上acquire
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //返回否 表示都不满足,有第三者线程占用
            return false;
        }
    

看下hasQueuedPredecessors方法

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

看下doug大叔写的这段判断逻辑

  1. 如果h==t则说明队列为空。必然为否,直接return false ,
  2. 如果h!=t则true && ??看后面判断。
  3. (1)若首节点(头结点的下一个节点)为空,取true,必然后面条件也取true,整体true&& true = true ;
  4. (2)若首节点不为空,且线程为当前线程,true && (false || false) = false;
  5. (3)若首节点不为空,且线程不为当前线程 则 true && (false || true) = true
  6. 则在1 4 情况下满足条件

3,addWaiter

当tryAcquire为否时,会调用addWaiter(Node.EXCLUSIVE)

  1. 先将当前线程打包成节点
  2. 如果队列中有尾结点不为空,尝试将使用CAS设置该节点为尾结点,成功直接return
  3. 不成功调用enq()方法,自旋设置该节点为尾结点
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;
    }

enq()方法

  1. 方法自旋
  2. 如果无尾结点,cas设置头结点,然后头尾节点都指向一个new NODE()
  3. 注意设置节点为尾结点流程为:先将该节点prev节点设置为当前尾结点,然后再CAS尝试设置节点为尾结点。注意:这里使用unsafe的compareAndSwapObject方法直接写内存,原子操作//TODO感觉这段CAS放进去有点问题,不是会覆盖t么
 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;
                }
            }
        }
    }
      /**
     * CAS tail field. Used only by enq.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

4,acquireQueued

当将当前线程放入队列尾部后,调用acquireQueued方法,
方法流程如下

  1. 自旋
  2. 判断当前节点的前节点如果为头节点,以及如果能获取到锁,则设置当前节点为头节点,return false,不调用selfInterrupt()方法
  3. 如果当前节点的前节点不是头结点,或者竞争失败,就会将node结点的waitStatus设置为-1(SIGNAL),并且park自己,等待前驱结点的唤醒。
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);
        }
    }

回顾下节点waitStatus 的SIGNAL枚举值(-1):告诉后续节点的线程需要进行unparking操作
shouldParkAfterFailedAcquire此方法解析:

  1. 如果前节点的状态刚好是SIGNAL,则刚好可以表示后面节点需要进行线程挂起操作,return true
  2. 如果不是SIGNAL,且大于了0,表示节点状态为CANCELLED,为需要删除的节点,则一直沿着节点向上寻找可以合适挂靠的节点,当发现有节点的状态小于等于0,则挂靠即:pred.next = node,其他可删除节点就会被垃圾回收器回收,return false,
  3. 如果发现节点为PROPAGATE(-3),CONDITION(-2),则设置该节点的状态为SIGNAL,return false
 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;
    }

调用parkAndCheckInterrupt方法,即将当前节点的线程挂起,

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

注意代码最后当如果没有节点获得锁时,需要执行cancelAcquire()方法,取消正在进行的尝试获取锁的操作

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

        node.thread = null;

        // Skip cancelled predecessors
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        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;

        // If we are the tail, remove ourselves.
        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.
            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
        }
    }

当最后获取不到锁则调用selfInterrupt()方法,设置当前线程挂起

 static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

四,公平锁解锁

ReentrantLock解锁调用unlock方法,则调用AQS的release方法,

 public void unlock() {
        sync.release(1);
    }

release方法先尝试调用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;
    }

tryRelease方法比较简单,就是将取出当前同步器状态然后减去1,由于存在可能锁的重入情况,如果减到c==0,设置当前独占线程为null,然后把值写回同步器状态

   protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

当tryRelease为true后,寻找头结点,调用unparkSuccessor方法,找到头结点的后续节点状态为小于0的节点,调用 LockSupport.unpark方法,将节点从挂起唤醒。

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

五,参考文章

深入理解Java中的AQS

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章