AbstractQueuedSynchronizer浅析

简介

JDK1.5之后,Java提供了Lock以及众多用于的并发开发的类(例如:ReentrantLock、CountDownLatch、CyclicBarrier等),而这些类的实现大部分都是基于AbstractQueuedSynchronizer(队列同步器,简称AQS)来实现。

AbstractQueuedSynchronizer内部有一个int变量表示的同步状态(同步状态通过getState setState compareAndSetState来维护,同时这三个方法能够保证线程安全),以及一个FIFO双向队列,一般称这个队列为同步队列。当线程获取资源(竞争同步状态)失败就会进去同步队列排队。

AQS是个抽象类(但是这个抽象类中并没有抽象方法),同步组件一般通过维护AQS的继承子类来实现。AQS既支持独占地获取同步状态,又支持共享地获取同步状态,从而实现不同类型的组件。

AQS是基于模板方法,同步组件需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。以下是AQS中可重写的方法。

  • protected boolean tryAcquire(int arg) : 独占式获取同步状态,试着获取,成功返回true,反之为false;
  • protected boolean tryRelease(int arg) :独占式释放同步状态,等待中的其他线程此时将有机会获取到同步状态;
  • protected int tryAcquireShared(int arg) :共享式获取同步状态,返回值大于等于0,代表获取成功;反之获取失败;
  • protected boolean tryReleaseShared(int arg) :共享式释放同步状态,成功为true,失败为false;
  • protected boolean isHeldExclusively() : 是否在独占模式下被线程占用。

这几个类在AQS中的默认类都是简单的抛出UnsupportedOperationException异常
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }

AQS实现分析

AQS的内部通过Node内部类一个个连接起来实现FIFO同步队列。Node类的结构如下所示:
AQS的静态内部类Node

waitStatus表示Node的状态,它的取值定义在Node中。另外AQS是共享还是独占地获取同步状态,在Node内中也定义了对应的标记属性。

static final  class Node {
    /** 共享模式 */
    static final AbstractQueuedSynchronizer.Node SHARED = new AbstractQueuedSynchronizer.Node();
    /** 独占模式 */
    static final AbstractQueuedSynchronizer.Node EXCLUSIVE = null;

    /** 表示线程已被取消(等待超时或者被中断) */
    static final int CANCELLED =  1;
    /** 表示后继节点中的线程需要被唤醒(unpaking) */
    static final int SIGNAL    = -1;
    /** 表示结点线程等待在condition上(等待队列),当被signal后,会从等待队列转移到同步到队列中 */
    static final int CONDITION = -2;
    /** 表示下一次共享模式下同步状态会被无条件地传播下去 */
    static final int PROPAGATE = -3;
    /** 除了上面定义的4中状态之外,waitStatus在初始化还能取值为0,即表示初始状态 */
}

独占模式获取同步状态

独占模式下获取同步状态的方法有:tryAcquireNanos(int arg, long nanosTimeout)、acquireInterruptibly(int arg)、acquire(int arg)。
tryAcquireNanos既能支持线程响应中断(即在同步队列中,如果线程被中断,则方法会抛出InterruptedException异常),又能支持超时;acquireInterruptibly方法只支持响应中断;acquire中断和超时都不支持。下面看看acquire(int arg)内部实现

public final void acquire(int arg) {
    /**
     * tryAcquire:获取同步状态,同步组件中的子类中自己重写实现
     * addWaiter:将当前线程放入Node节点中,再将Node节点放入同步队列中
     * acquireQueued:在同步队列中自旋获取同步状态,这个方法会使节点中的线程阻塞等待
     */
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
        selfInterrupt();
}

acquire方法中做了如下几件事

  • 尝试获取同步状态,如果成功直接返回,失败则继续;
  • 构造Node节点,将当前线程放入其中;
  • 将构造好的Node节点放入由Node组成的同步队列;
  • Node中的线程在同步队列中自旋等待。

tryAcquire(int arg)方法的作用就是让同步组件自己实现的,所以实现得看具体组件。下面看看addWaiter方法是如何实现的


CAS设置尾节点
private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node mode) {
    //使用当前线程构造node节点
    AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(
            Thread.currentThread(), mode);
    AbstractQueuedSynchronizer.Node pred = tail;
    //队列不为空
    if (pred != null) {
        node.prev = pred;
        /* 通过cas将当前节点放入同步队列的尾部,因为多线程环境下获取锁成功的线程只有一个,
         * 所以会有多个线程需要放入同步队列尾部,因此此处需要通过cas保证线程安全。
         * 同时compareAndSetTail可能失败,所以下面得调用enq(node)方法,执行重复的代码
         */
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

private AbstractQueuedSynchronizer.Node enq(final AbstractQueuedSynchronizer.Node node) {
    for (;;) {  //通过死循环和compareAndSetTail方法保证当前线程一定能放入队尾
        AbstractQueuedSynchronizer.Node t = tail;
        if (t == null) { 
            if (compareAndSetHead(new AbstractQueuedSynchronizer.Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

enq内部是个死循环,通过CAS设置尾结点,不成功就一直重试,很经典的CAS自旋的用法。

acquireQueued方法会使得同步队列中的每个节点自旋获取同步状态,具体实现如下

final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋等待
        for (;;) {
            //找到当前结点的前驱结点
            final AbstractQueuedSynchronizer.Node p = node.predecessor();
            // 只有前驱节点是head,才尝试获取同步状态
            if (p == head && tryAcquire(arg)) {
                //获取同步状态成功,将当前结点设置为头结点。
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            /**
             * shouldParkAfterFailedAcquire: 判断是否应该阻塞当前线程
             * parkAndCheckInterrupt:真正让线程阻塞等待
             * 阻塞之前先判断是否能阻塞
             */
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

acquireQueued内部自旋,判断当前节点是否为老二节点(head是老大节点),当前节点是老二节点就去尝试获取同步状态,如果获取成功则返回,否则的话阻塞当前线程。
线程阻塞通过LockSupport.park(this);来实现。但是在调用阻塞之前会先通过shouldParkAfterFailedAcquire方法通过waitStatus的值来判断是否需要调用阻塞。

private static boolean shouldParkAfterFailedAcquire(AbstractQueuedSynchronizer.Node pred, AbstractQueuedSynchronizer.Node node) {
    //获取前驱节点的waitStatus值
    int ws = pred.waitStatus;
    //若前驱结点的状态是SIGNAL,意味着当前结点可以被安全地唤醒(park)
    if (ws == AbstractQueuedSynchronizer.Node.SIGNAL)
        return true;
    if (ws > 0) {
        /* ws>0,只有CANCEL状态ws才大于0。若前驱结点处于CANCEL状态,也就是此结点线程
         * 已经无效,从后往前遍历,找到一个非CANCEL状态的结点,将自己设置为它的后继结点
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 若前驱结点为其他状态,将其设置为SIGNAL状态
        compareAndSetWaitStatus(pred, ws, AbstractQueuedSynchronizer.Node.SIGNAL);
    }
    return false;
}

可以看到只有前驱节点的waitStatus是SIGNAL,当前才需要调用阻塞。
整个独占模式下获取同步状态的流程图如下


acquire方法执行流程

独占模式释放同步状态

线程执行完自己的逻辑之后,会释放同步状态。独占模式下释放同步状态的方法如下

public final boolean release(int arg) {
    //尝试释放同步状态,成功的话唤醒后继节点,该方法使用者要重写
    if (tryRelease(arg)) {
        AbstractQueuedSynchronizer.Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

unparkSuccessor的实现如下

private void unparkSuccessor(AbstractQueuedSynchronizer.Node node) {
    // 获取waitStatus,并将其设置为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    // 获取头节点的后继节点
    AbstractQueuedSynchronizer.Node s = node.next;
    if (s == null || s.waitStatus > 0) { // waitStatus>0表示取消状态
        s = null;
        //从队尾往前遍历找到一个处于正常阻塞状态的结点进行唤醒
        for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        // 相当于通知(notify),将后继节点唤醒
        LockSupport.unpark(s.thread);
}

释放同步状态时,会将头节点的后继节点唤醒。

共享模式获取同步状态

共享式同步组件来讲,同一时刻可以有多个线程同时获取到同步状态。

public final void acquireShared(int arg) {
    // tryAcquireShared尝试获取同步状态,使用者需要重写实现
    if (tryAcquireShared(arg) < 0)
        //代码执行到这里,表示同步状态获取失败,需要排队
        doAcquireShared(arg);
}

private void doAcquireShared(int arg) {
    // 构建共享节点,放入同步队列中
    final AbstractQueuedSynchronizer.Node node = addWaiter(AbstractQueuedSynchronizer.Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 前驱节点
            final AbstractQueuedSynchronizer.Node p = node.predecessor();
            //只有前驱是头结点,才有机会尝试获取同步状态
            if (p == head) {
                //尝试获取同步状态
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    ////获取成功就将当前结点设置为头结点,若还有可用资源,传播下去,也就是继续唤醒后继结点
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

可以看到,共享模式下的同步状态获取基本和独占模式类似。

共享模式释放同步状态

共享模式下,由于有多个线程持有同步状态,所以释放的时候需要保证线程安全。

private void doReleaseShared() {
    //死循环,持有同步状态的线程可能有多个,采用循环CAS保证线程安全
    for (;;) {
        AbstractQueuedSynchronizer.Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 当头节点的ws为SIGNAL才唤醒后继节点
            if (ws == AbstractQueuedSynchronizer.Node.SIGNAL) {
                // 将头节点的waitStatus置为0
                if (!compareAndSetWaitStatus(h, AbstractQueuedSynchronizer.Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒后继节点
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                    !compareAndSetWaitStatus(h, 0, AbstractQueuedSynchronizer.Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

通过AQS实现一个自定义锁

在我们的自定义锁中,维护一个AQS子类的内部类,该AQS子类重写tryAcquire(int acquire)和tryRelease(int releases)方法。然后加锁调用AQS的模板方法acquire(int arg),释放锁调用AQS的模板方法release(int arg)即可。具体实现如下

public class Mutex implements Lock,Serializable {

    // 定义AQS的子类,主要依靠这个子类来实现定义锁
    private static class Sync extends AbstractQueuedSynchronizer {
      // 锁是否被占用
      protected boolean isHeldExclusively() { 
        return getState() == 1; 
      }

      // state=0时,获取锁,state+1
      public boolean tryAcquire(int acquires) {
        assert acquires == 1; // Otherwise unused
       if (compareAndSetState(0, 1)) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
      }

      // 释放锁,让state置为0
      protected boolean tryRelease(int releases) {
        assert releases == 1; // Otherwise unused
        if (getState() == 0) throw new IllegalMonitorStateException();
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
      }
       
      // Provide a Condition
      Condition newCondition() { return new ConditionObject(); }

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

参考
《Java并发编程的艺术》

你可能感兴趣的:(AbstractQueuedSynchronizer浅析)