AQS源码分析及核心方法解析

1 概述

AQS提供了一个基于FIFO队列实现锁和同步器的基本框架。

j.c.u中使用AQS的类有:

  • CountDownLatch.Sync
  • ThreadPoolExecutor.Worker
  • ReentrantLock.FairSync/NonfairSync
  • ReentrantReadWriteLock.FairSync/NonfairSync
  • Semaphore.Sync

2 AQS如何使用

2.1 相关概念

state:同步状态值,用于子类中实现状态记录;

acquire:修改state,可以理解为获取锁;

release:与acquire相反操作的修改state,可以理解为释放锁。

predecessor:前驱,successor:后继

Node.EXCLUSIVE:独占模式, Node.SHARED:共享模式

2.2 实现约定

使用AQS实现一个锁或同步器,需要依据以下几点去实现:

  • 1 同步器需要以一个单独的数字表示状态。

  • 2 同步器需要定义一个继承AQS的内部类去实现同步属性。

  • 3 内部类继承AQS后,必须根据需要实现 tryAcquire*/tryRelease* 去改变state。

  • 4 有exclusive mode(默认)和shared两种模式:

    • exclusive mode:独占模式,其他线程尝试acquire不会成功;
    • shared mode:共享模式,多个线程尝试acquire都会成功。
  • 5 一般只支持其中一种模式即可,但有特例:ReadWriteLock同时支持两种模式。

  • 6 AbstractQueuedSynchronizer.ConditionObject 可以在子类中作为Condition的实现使用。

2.3 使用步骤

docs.oracle.com/javase/8/do…

2.3.1 独占锁

基本步骤如下:

  • 1 创建内部类继承AbstractQueuedSynchronizer;
  • 2 实现tryAcquire/tryRelease
  • 3 lock时调用acquire,unlock时调用release
  • 4 isLocked时调用isHeldExclusively
  • 5 hasQueuedThreads时调用hasQueuedThreads

官方示例如下:

class Mutex implements Lock, java.io.Serializable {

   // Our internal helper class
   private static class Sync extends AbstractQueuedSynchronizer {
     // Reports whether in locked state
     protected boolean isHeldExclusively() {
       return getState() == 1;
     }

     // Acquires the lock if state is zero
     public boolean tryAcquire(int acquires) {
       assert acquires == 1; // Otherwise unused
       if (compareAndSetState(0, 1)) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
     }

     // Releases the lock by setting state to zero
     protected boolean tryRelease(int releases) {
       assert releases == 1; // Otherwise unused
       if (getState() == 0) throw new IllegalMonitorStateException();
       setExclusiveOwnerThread(null);
       setState(0);
       return true;
     }

     // Provides a Condition
     Condition newCondition() { return new ConditionObject(); }

     // Deserializes properly
     private void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException {
       s.defaultReadObject();
       setState(0); // reset to unlocked state
     }
   }

   // The sync object does all the hard work. We just forward to it.
   private final Sync sync = new Sync();

   public void lock()                { sync.acquire(1); }
   public boolean tryLock()          { return sync.tryAcquire(1); }
   public void unlock()              { sync.release(1); }
   public Condition newCondition()   { return sync.newCondition(); }
   public boolean isLocked()         { return sync.isHeldExclusively(); }
   public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
   public void lockInterruptibly() throws InterruptedException {
     sync.acquireInterruptibly(1);
   }
   public boolean tryLock(long timeout, TimeUnit unit)
       throws InterruptedException {
     return sync.tryAcquireNanos(1, unit.toNanos(timeout));
   }
 }
复制代码
2.3.2 共享锁

基本步骤如下:

  • 1 创建内部类继承AbstractQueuedSynchronizer;
  • 2 实现tryAcquireShared/tryReleaseShared
  • 3 await时调用acquireShared,signal时调用releaseShared

官方示例如下:

class BooleanLatch {

   private static class Sync extends AbstractQueuedSynchronizer {
     boolean isSignalled() { return getState() != 0; }

     protected int tryAcquireShared(int ignore) {
       return isSignalled() ? 1 : -1;
     }

     protected boolean tryReleaseShared(int ignore) {
       setState(1);
       return true;
     }
   }

   private final Sync sync = new Sync();
   public boolean isSignalled() { return sync.isSignalled(); }
   public void signal()         { sync.releaseShared(1); }
   public void await() throws InterruptedException {
     sync.acquireSharedInterruptibly(1);
   }
 }
复制代码

3 主要属性

FIFO队列,由Node节点组成的链表:

           +------+  prev +-----+       +-----+
      head |      | <---- |     | <---- |     |  tail
           +------+       +-----+       +-----+

3.1 head

private transient volatile Node head;

等待队列的队头,延迟初始化。除了初始化的时候,仅当调用方法setHead(Node node)时才会修改。

如果head已经存在,保证waitStatus不会是CANCELLED状态。

相关方法:

  • setHead(Node node)
  • setHeadAndPropagate(Node node, int propagate)
  • compareAndSetHead(Node update)

3.2 tail

private transient volatile Node tail;

等待队列的队尾, 延迟初始化。

仅当调用方法enq(Node)添加新的等待节点时才会修改。

相关方法:

  • enq(final Node node)
  • findNodeFromTail(Node node)
  • compareAndSetTail(Node expect, Node update)

3.3 state

private volatile int state;

同步状态。

相关方法

  • getState()
  • setState(int newState)
  • compareAndSetState(int expect, int update)

4 Node

4.1 属性

4.1.1 waitStatus

volatile int waitStatus:状态

  • CANCELLED = 1 : 表示当前节点被取消。

当前节点被取消,比如线程park后timeout或interrupt。

当一个节点状态变为CANCELLED后,不会再变成其他状态,其线程也不会再次被阻塞。

  • SIGNAL = -1 : 表示当前节点的 后续节点的线程 需要被唤醒运行(unpark)。

说明当前节点的 后续节点 是block状态(或即将是block状态),即其线程被park了。

所以当前节点在release或cancel时,必须unpark它的后续节点。

为了避免竞争,acquire方法必须先表明需要一个信号,然后再尝试acquire,失败则阻塞。

  • CONDITION = -2 :

表示当前节点的线程在condition队列中(ConditionObject类中设置),该节点直到其状态被设置为0时,才会转变成为同步队列中的节点。

  • PROPAGATE = -3 : 当前场景下后续的acquireShared将会无条件被传播。

releaseShared(共享式的释放同步状态)需要被传播给其他节点;该状态在 doReleaseShared方法中被设置(仅仅适用于头节点)以保证传播,即使其它操作介入也不会中断。

  • 0 : 当前节点在同步队列中,等待acquire。

waitStatus在同步队列中默认是0,condition队列中默认是CONDITION(-2)。

4.1.2 prev

volatile Node prev:前驱节点,只对非ConditionObject外的节点定义启用。

相关方法

  • Node predecessor():返回当前节点的前置节点;
  • boolean hasQueuedPredecessors():当前节点(当前线程所在的节点)是否有前置节点。
4.1.3 next

volatile Node next:后继节点;只对非ConditionObject外的节点定义启用,所以next的waitStatus不会是CONDITION。

相关方法

  • void unparkSuccessor(Node node):uppark节点node的后继节点。
4.1.4 thread

volatile Thread thread:当前节点的线程,Initialized on construction and nulled out after use.

4.1.5 nextWaiter

Node nextWaiter:condition队列中的后继节点,只对ConditionObject内的节点定义启用,即当waitStatus==CONDITION时,才有值。

4.2 同步队列&Condition队列中Node的区别

比较 同步队列 Condition队列
使用的字段 waitStatus/thread/prev/next waitStatus/thread/nextWaiter
waitStatus初始值 0 CONDITION
waitStatus可选值 CANCELLED/SIGNAL/PROPAGATE/0 CANCELLED/CONDITION

5 AQS子类的实现约定:

  • 根据需要,实现tryAcquire*/tryRelease*方法;
  • 使用getState/setState(int)/compareAndSetState(int,int) 去监视或修改同步状态。

5.1 tryAcquire(int)

独占模式下尝试acquire。

这个方法实现时,需要查询当前状态是否允许acquire,然后再使用compareAndSetState进行acquire。

这个方法在acquire(int arg)中被调用。如果调用失败,并且调用线程没在队列中,acquire方法会将该线程放入队列,直到被其他线程执行release释放。通常用来实现Lock.tryLock()。

返回值:true表示执行成功(state更新成功,成功acquire)。

5.2 tryRelease(int)

独占模式下,尝试修改state进行release。

返回值:true表示当前对象是一个完全release的状态,其他任何等待线程都可以尝试acquire。

5.3 tryAcquireShared(int)

共享模式下尝试acquire。

返回值:

  • 负数:获取失败;
  • 0 : 获取成功,但接下来共享模式下的请求不会成功,即没有剩余资源可用;
  • 正数:获取成功,接下来共享模式下的acquire仍有可能成功, 即仍有剩余资源可用。 剩下的的等待线程必须检测对象的状态是否允许acquire。

5.4 tryReleaseShared(int)

共享模式下,尝试修改state进行release。

返回值:true表示share mode下release成功,即允许一个等待的acquire(shared or exclusive)成功进行。

5.5 isHeldExclusively()

当前线程是否以独占方式进行了同步(即状态被占用),也就是说,当前线程是否在独占资源。

这个方法在ConditionObject的方法中被调用,所以只有用到condition才需要去实现。

以上5个方法,默认的实现都是抛出 UnsupportedOperationException.

6 核心方法

6.1 addWaiter

过程:

  • 1 使用当前线程给定模式创建节点;
  • 2 新创建的节点加在队尾,成为新的tail;
  • 3 返回创建的新节点

参数mode:Node.EXCLUSIVENode.SHARED

private Node addWaiter(Node mode) {
        // 创建节点
        Node node = new Node(Thread.currentThread(), mode);
        
        Node pred = tail;
        
        if (pred != null) {
            // tail不为null,处理流程和enq(Node)中tail!=null时的处理流程一样。
            
            node.prev = pred; //当前节点的前驱指向现在的tail
            if (compareAndSetTail(pred, node)) {  //将tail对象由原来的tail修改为当前新节点
                pred.next = node; //原来的tail的next指向当前节点
                return node;
            }
        }
        // tail为null则调用enq方法。
        enq(node);
        return node;
    }
复制代码

6.2 enq

过程:

  • 1 先判断tail,如果tail是null:初始化head,并将tail指向head同一个Node;然后重新循环;
  • 2 将新节点(参数node)加入到队尾;
  • 3 返回原来的tail(即新节点的前置节点)
/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     * 插入新节点,需要的话初始化;参数是新增的节点,返回值是这个节点的前驱(即原来的tail)。
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { 
            
               // tail为null,初始化tail/head。
                if (compareAndSetHead(new Node()))
                    tail = head; //tail指向head
            } else {
                node.prev = t;  //当前节点prev置指向原来的tail
                if (compareAndSetTail(t, node)) { // 将tail对象由原来的tail修改为当前新节点
                    t.next = node; //原来的tail的next指向当前节点
                    return t;
                }
            }
        }
    }
复制代码

这里有个疑问,如果tail是null,但是head不是null,则tail不是一直不会被初始化了吗?

初始化head的方法是compareAndSetHead,而根据compareAndSetHead里的注释,只在enq中调用:

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

所以:head和tail,是在enq方法中同时初始化的。

6.3 shouldParkAfterFailedAcquire

acquire失败后,判断node是否应该block。

过程:

如果node的前置pred的waitStatus == Node.SIGNAL,那么node应该block;否则做以下处理:

  • 1 如果pred.waitStatus == Node.CANCELLED:说明前置节点已经取消。持续向前查询,直到找到一个waitStatus <= 0的节点,作为node的新前置,并把CANCELLED的几点从队列中取消;
  • 2 如果pred.waitStatus是0或PROPAGATE(不会是CONDITION):因为0是初始状态,PROPAGATE是传播状态,所以前置如果是这两种状态,都应该将前置的状态修改为SIGNAL。但不应该park。

参数pred必须是参数node的前置

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        
        // 前驱节点的状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            // 当前节点(即pred的下一个节点)已经是等待状态,需要一个release去唤醒它。所以node可以安全的park.
            return true;
        if (ws > 0) { // CANCELLED
            /*
               waitStatus > 0 说明是canceled状态。
               前驱是canceled状态,则表明前驱节点已经超时或者被中断,需要从同步队列中取消。
               持续向前找,直到找到waitStatus<=0的节点,作为node的前驱.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
              前驱节点waitStatus为 0 或 PROPAGATE,置换为SIGNAL.
              不会是CONDITION,因为CONDITION用在ConditionObject中。
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
复制代码

6.4 unparkSuccessor

唤醒当前节点的后续节点

过程:

  • 1 如果节点不是CANCELLED,则将waitStatus设置为0;
  • 2 如果后续节点是null或是CANCELLED状态,则对node之后第一个满足条件(waitStatus <= 0)的节点unpark。
private void unparkSuccessor(Node node) {
       
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0); // waitStatus设置为0

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            /*
             node的后继节点是null或是CANCELLED状态,释放该后继节点;
             并从tail开始向前找,找到离node最近的一个非CANCELLED(waitStatus <= 0)的节点,作为要unpark的节点。
            */
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 并unpark后继节点。
        if (s != null)
            LockSupport.unpark(s.thread);
    }
复制代码

6.5 acquireQueued

独占模式下,已经在队列中的线程进行acquire。用在acquire方法或者condition wait方法中。

过程:

  • 1 只有node的前驱是head时,才轮到node尝试获取锁;
  • 2 否则检查node是否应该park(node的前置节点waitStatus是不是等于SIGNAL),是的话则park;然后等待其他线程对当前线程unpark。

返回是否中断(park)。

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                /*
                只有前驱节点是head时,才轮到该节点争夺锁。
                当前节点的前驱是head 并且 tryAcquire成功:
                把当前要获取的节点设置为head,
                */
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                /*
                如果当前节点不是head 或 tryAcquire失败,则检验当前节点的线程(此时即当前线程)是否应park。
                parkAndCheckInterrupt会park当前线程,因此在这里block;将来有其他线程对当前线程unpark时,将继续此循环。
                否则继续循环尝试acquire。
                */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
复制代码

6.6 isOnSyncQueue

主要用于Condition流程中的判断,返回节点是否在同步队列中:


// Internal support methods for Conditions

    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            // waitStatus是CONDITION,或prev是null
            return false;
        if (node.next != null) 
            // 根据文章前面对Node的描述,next只会在同步队列中有值,nextWaiter只会在condition队列中有值。
            return true;
        /*
           因为CAS操作替换值的时候可能会失败,所以有可能出现:
           node.prev不为null,但是没在同步队列中。
           
           所以需要从队尾向前再遍历一遍。
        return findNodeFromTail(node);
    }

    /**
     从同步队列的tail开始向前遍历,查找是否有节点node。
     */
    private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }
复制代码

7 独占模式同步

独占模式,需要子类的内部类,实现tryAcquire/tryRelease,并在lock和unlock时调用acquire/release,如ReentrantLock.FairSync

7.1 acquire

  • 1 tryAcquire(arg) 是否成功
  • 2 不成功就调用addWaiter创建Node,并调用acquireQueued加入节点
  • 3 acquireQueued成功的话就selfInterrupt.
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
复制代码

7.2 release

  • 1 执行tryRelease,成功则继续执行,失败则返回false;
  • 2 head不为空且nextWaiter!=0(0是同步队列中的节点的nextWaiter的初始值),则执行unparkSuccessor唤醒后继者,然后返回true。
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//unpark节点head的后继者
            return true;
        }
        return false;
    }
复制代码

8 共享模式同步

共享模式,需要子类的内部类,实现tryAcquireShared/tryReleaseShared,并在lock和unlock时调用acquireShared/tryReleaseShared,如CountDownLatch.Sync

8.1 acquireShared

tryAcquireShared失败,则执行doAcquireShared。

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

复制代码
    private void doAcquireShared(int arg) {
        
        // 创建Node.SHARED模式的新节点(添加在队尾)
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            
            // 循环
            for (;;) {
            /*
            1 获取当前节点的前置节点;
            2 前置节点是head,则tryAcquireShared,如果成功,则将当前节点设置为head,并将结果向后传播。
            */
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                    
                        //设置head值,并将tryAcquireShared结果向后传播
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                
                // 前置节点不是head,当前线程是否park;
                // 如果park当前线程,将来有其他线程对当前线程unpark时,将继续此循环。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
复制代码

8.2 releaseShared

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
复制代码

猜你喜欢

转载自juejin.im/post/5dea57f3518825122c4c9ba2