[转载]JUC—AbstractQueuedSynchronizer(AQS)源码深度解析与应用案例

Scroll Down

从AQS学起

public abstract class AbstractQueuedSynchronizer
  extends AbstractOwnableSynchronizer
  implements Serializable

AbstractQueuedSynchronizer,来自于JDK1.5,位于JUC包,由并发编程大师Doug Lea编写,字面翻译就是“抽象队列同步器”,简称为AQS。AQS作为一个抽象类,是构建JUC包中的锁(比如ReentrantLock)或者其他同步组件(比如CountDownLatch)的底层基础框架。
  在每一个同步组件的实现类中,都具有AQS的实现类作为内部类,被用来实现该锁的内存语义,并且他们之间是强关联关系,从对象的关系上来说锁或同步器与AQS是:“聚合关系”。
  也可以这样理解二者之间的关系:锁(比如ReentrantLock)是面向使用者(大部分“用轮子”程序员)的,它定义了使用者与锁交互的外部接口,比如获得锁、释放锁的接口,这样就隐藏了实现细节,方便学习使用;而AQS则是面向的是锁的实现者(少部分“造轮子”的程序员,比如Doug Lea,这个比喻并不恰当,因为Doug Lea是整个JUC包的编写者,包括AQS也是他写的),因为AQS简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、线程的等待与唤醒等更加底层的操作,我们如果想要自己写一个“锁”—造轮子,那么就可以直接利用AQS框架实现,而不需要去关心上面那些更加底层的东西。这样的话似乎也不算真正的从零开始造轮子,因为我们用的AQS这个制造工具也是别人(Doug Lea)制作的::>_<::。
  锁和AQS很好地隔离了使用者和实现者所需关注的领域。如果我们只是想单纯的使用某个锁,那个直接看锁的API就行了,而如果我们想要看懂某个锁的实现,那么我们就需要看锁的源码,在这之中我们又可能会遇到AQS框架的某个方法的调用;如果我们想要走得更远,那么此时又会进入AQS的源码,那么我们必须去了解AQS这个同步框架的设计与实现!
  如果我们想要真正学搞懂JUC的locks部分,那么,先从AQS学起吧!

AQS的设计

  AbstractQueuedSynchronizer被设计为一个抽象类,它使用了一个volatile int类型的成员变量state来表示同步状态,通过内置的FIFO双向队列来完成资源获取线程的排队等待工作。通常AQS的子类通过继承AQS并实现它的抽象方法来管理同步状态。
  AQS的子类常常作为同步组件的静态内部类(也就是聚合关系),AQS自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供同步组件使用,AQS既可以支持独占式地访问同步状态(比如ReentrantLock),也可以支持共享式地访问同步状态(比如CountDownLatch),这样就可以方便实现不同类型的同步组件。
  AQS的方法设计是基于模板方法模式的,也就是说实现者需要继承AQS并按照需要重写指定的方法,随后将AQS的实现类组合在同步组件的实现中,并最终调用AQS提供的模板方法来实现同步,而这些模板方法内部实际上被设计成会调用使用者重写的方法。
  因此,AQS的方法可以分为三大类:固定方法、可重写的方法、模版方法。

固定方法

  重写AQS指定的方法时,需要使用AQS提供的如下3个方法来访问或修改同步状态,不同的锁实现都可以直接调用这三个方法:

  • getState():获取当前最新同步状态。
  • setState(int newState):设置当前最新同步状态。
  • compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性。

它们的源码很简单:

/**
 * int类型同步状态变量,或者代表共享资源,被volatile修饰,具有volatile的读、写的内存语义
 */
private volatile int state;

/**
 * @return 返回同步状态的当前值。此操作具有volatile读的内存语义,因此每次获取的都是最新值
 */
protected final int getState() {
    return state;
}

/**
 * @param newState 设置同步状态的最新值。此操作具有volatile写的内存语义,因此每次写数据都是写回主存并导致其它缓存实效
 */
protected final void setState(int newState) {
    state = newState;
}

/**
 * 如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值。
 * 此操作具有volatile读取和写入的内存语义。
 *
 * @param expect 预期值
 * @param update 写入值
 * @return 如果更新成功返回true,失败则返回false
 */
protected final boolean compareAndSetState(int expect, int update) {
    //内部调用unsafe的方法,该方法是一个CAS方法
    //这个unsafe类,实际上是比AQS更加底层的底层框架,或者可以认为是AQS框架的基石。
    //CAS操作在Java中的最底层的实现就是Unsafe类提供的,它是作为Java语言与Hospot源码(C++)以及底层操作系统沟通的桥梁
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

这三个方法getState()、setState()、compareAndSetState()都是final方法,是AQS提供的通用设置同步状态的方法,能保证线程安全,我们直接调用即可。

可重写的方法

  可重写的方法在AQS中一般都没有提供实现,并且如果子类不重写直接调用还会抛出异常,这些方法一般是对同步状态的单次尝试获取、释放(即加锁、解锁),并没有后续失败处理的方法!实现者一般根据需要重写对应的方法!
  AQS可重写的方法如下所示:

/**
 * 独占式获取锁,该方法需要查询当前状态并判断锁是否符合预期,然后再进行CAS设置锁。返回true则成功,否则失败。
 *
 * @param arg 参数,在实现的时候可以传递自己想要的数据
 * @return 返回true则成功,否则失败。
 */
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

/**
 * 独占式释放锁,等待获取锁的线程将有机会获取锁。返回true则成功,否则失败。
 *
 * @param arg 参数,在实现的时候可以传递自己想要的数据
 * @return 返回true则成功,否则失败。
 */
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

/**
 * 共享式获取锁,返回大于等于0的值表示获取成功,否则失败。
 *
 * @param arg 参数,在实现的时候可以传递自己想要的数据
 * @return 返回大于等于0的值表示获取成功,否则失败。
 * 如果返回值小于0,表示当前线程共享锁失败
 * 如果返回值大于0,表示当前线程共享锁成功,并且接下来其他线程尝试获取共享锁的行为很可能成功
 * 如果返回值等于0,表示当前线程共享锁成功,但是接下来其他线程尝试获取共享锁的行为会失败(实际上也有可能成功,在后面的源码部分会将)
 */
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

/**
 * 共享式释放锁。返回true成功,否则失败。
 *
 * @param arg 参数,在实现的时候可以传递自己想要的数据
 * @return 返回true成功,否则失败。
 */
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

/**
 * 当前AQS是否在独占模式下被线程占用,一般表示是否被前当线程独占;如果同步是以独占方式进行的,则返回true;其他情况则返回 false
 *
 * @return 如果同步是以独占方式进行的,则返回true;其他情况则返回 false
 */
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

模版方法

  在实现同步组件的时候,按照需要重写可重写的方法,但是直接调用的还是AQS提供的模板方法,模版方法再被Lock接口的方法包装。
  这些模板方法同样是final的。可以猜测出这些模版方法包含了对上面的可重写方法的后续处理(比如失败处理)!AQS的模板方法基本上分为3类:

独占式获取与释放同步状态
共享式获取与释放同步状态
查询同步队列中的等待线程情况
独占方式:
acquire(int arg): 独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg) 方法。该方法不会响应中断。
acquireInterruptibly(int arg): 与acquire(int arg) 相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前被中断,则该方法会抛出InterruptedException并返回。
tryAcquireNanos(int arg,long nanos): 在acquireInterruptibly基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回false,获取到了返回true。
release(int arg) : 独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个结点包含的线程唤醒。

共享方式:
acquireShared(int arg): 共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待。与独占式的不同是同一时刻可以有多个线程获取到同步状态。该方法不会响应中断。
acquireSharedInterruptibly(int arg) : 与acquireShared(int arg) 相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前被中断,则该方法会抛出InterruptedException并返回。
tryAcquireSharedNanos(int arg,long nanos): 在acquireSharedInterruptibly基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回false,获取到了返回true
releaseShared(int arg): 共享式释放同步状态

获取线程等待情况:
getQueuedThreads() : 获取等待在同步队列上的线程集合。

总体结构

  AQS中文名为队列同步器,可以猜测,它的内部具有一个队列,实际上也确实如此。
  AQS内部使用一个一个FIFO的双端队列,被称为同步队列,来完成同步状态的管理,当前线程获取同步状态失败(获取锁失败)的时候,AQS会将当前线程及其等待状态信息构造成一个结点Node并将其加入同步队列中,同时阻塞当前线程,当同步状态由持有线程释放的时候,会将同步队列中的首结点中的线程唤醒使其再次尝试获取同步状态。
  同步队列中的结点Node是AQS中的一个内部类,用来保存获取同步状态失败的线程的线程引用、等待状态以及前驱结点和后继结点。AQS外部持有同步队列的两个引用,一个指向头结点head,而另一个指向尾结点tail。
  在AQS中还维持了一个volatile int类型的字段state,用来描述同步状态,可以通过getState、setState、compareAndSetState函数修改其值。
  对于不同的锁实现,state可以有不同的含义。对于ReentrantLock 的实现来说,state可以用来表示当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock 来说,state 的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数;对于Semaphore来说,state用来表示当前可用信号的个数:对于CountDownlatch 来说,state 用来表示计数器当前的值。
  AQS内部还有一个ConditionObject内部类,用来结合锁实现更加灵活的线程同步。ConditionObject 可以直接访问AQS 对象内部的变量,比如state 状态值和AQS 同步队列。ConditionObject 又被称为条件变量,每个条件变量实例又对应一个条件队列(单向链表,又称等待队列),其用来存放调用Condition的await方法后被阻塞的线程,这个等待队列的头、尾元素分别由firstWaiter 和lastWaiter持有。
  上面的介绍能看出来,AQS中包含两个队列的实现,一个同步队列,用于存放获取不到锁的线程,另一个是条件队列,用于存放调用了await方法的线程,但是两个队列中的线程都是WAITING状态,因为Lock所底层都是调用的LockSupport.park方法。后面的章节会介绍同步队列和条件队列的实现!

Lock接口

Lock接口概述

public interface Lock

Lock接口本来和AQS没有太多关系的,但是如果想要是实现一个正规的、通用的同步组件(特别是锁),那就不得不提Lock接口。
  Lock接口同样自于JDK1.5,它被描述成JUC中的锁的超级接口,所有的JUC中的锁都会实现Lock接口。
  由于它是作为接口,到这里或许大家都明白了它的设计意图,接口就是一种规范。Lcok接口定义了一些抽象方法,用于获取锁、释放锁等,而所有的锁都实现Lock接口,那么它们虽然可能有不同的内部实现,但是开放给外部调用的方法却是一样的,这就是一种规范,无论你怎么实现,你给外界调用的始终是“同一个方法”!因此JUC中的锁也被常常统称为lock锁。
  这种优秀的架构设计,不同的锁实现统一了锁获取、释放等常规操作的方法,方便外部人员使用和学习!类似的设计在JDBC数据库驱动上面也能看到!

Lock接口的API方法

  我们来看看实现Lcok接口都需要实现哪些方法!

方法名称描述
lock获取锁,如果锁无法获取,那么当前的线程被挂起,直到锁被获取到,不可被中断。
lockInterruptibly获取锁,如果获取到了锁,那么立即返回,如果获取不到,那么当前线程被挂起,直到当前线程被唤醒或者其他的线程中断了当前的线程。
tryLock如果调用的时候能够获取锁,那么就获取锁并且返回true,如果当前的锁无法获取到,那么这个方法会立刻返回false
tryLcok(long time,TimeUnit unit)在指定时间内尝试获取锁。如果获取了锁,那么返回true,如果当前的锁无法获取,那么当前的线程被挂起,直到当前线程获取到了锁或者当前线程被其他线程中断或者指定的等待时间到了。时间到了还没有获取到锁则返回false。
unlock释放当前线程占用的锁
newCondition返回一个与当前的锁关联的条件变量。在使用这个条件变量之前,当前线程必须占用锁。调用Condition的await方法,会在等待之前原子地释放锁,并在等待被唤醒后原子的获取锁

锁获取与中断

  Thread类中有一个interrupt方法,可中断因为主动调用Object.wait()、Thread.join()和Thread.sleep()等方法造成的线程等待,以及使用lockInterruptibly方法和tryLock(time,timeUnit)尝试获取锁但是未获取锁而造成的阻塞,并且他们都将抛出InterruptedException异常,并且设置该线程的中断状态位为false。对于因为调用lock()方法,或者因为无法获取Synchronized锁而被阻塞的线程,interrupt方法无法中断,仅会设置中断标志位为true,另外对于正常线程,同样仅会设置中断标志位为true,通知该线程应该被中断了。有一个特例是:被LockSupport.park()阻塞的线程也可以被中断,但是不会抛出异常,并且不会恢复标志位。
  下面举例详细说明Lock接口的这四种方法的使用:假如线程A和线程B使用同一个锁LOCK,此时线程A首先获取到锁LOCK.lock(),并且始终持有不释放。如果此时B要去获取锁,有四种方式:

LOCK.lock(): 此方式会使得B始终处于等待中,即使调用B.interrupt()也不能中断,除非线程A调用LOCK.unlock()释放锁。
LOCK.lockInterruptibly(): 此方式会使得B等待,但当调用B.interrupt()会被中断等待,并抛出InterruptedException异常,否则会与lock()一样始终处于等待中,直到线程A释放锁。
LOCK.tryLock(): 调用该方法时B不会等待,一次获取不到锁就直接返回false。
LOCK.tryLock(10, TimeUnit.SECONDS): 该处会在10秒时间内处于等待中,但当调用B.interrupt()会中断等待,并抛出InterruptedException。 10秒时间内如果线程A释放锁,线程B会获取到锁并返回true,否则10秒过后会直接返回false,去执行下面的逻辑。

Synchronized和Lock的区别

首先synchronized是java内置关键字,是jvm层面实现的;Lock是个java接口,可以代表JUC中的锁,通过java代码实现。
synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;即lock需要显示的获得、释放锁,synchronized隐式获得、释放锁。
lock在等待锁过程中可以用lockInterruptibly()方法配合interrupt方法()来中断等待,也可以使用tryLock()设置等待超时时间,而synchronized只能等待锁的释放,不能响应中断。
synchronized是非公平锁,而Lock锁可以实现为公平锁或者非公平锁。
Lock锁将监视器monitor方法,单独的封装到了一个Condition对象当中,更加的面向对象了,而且一个Lock锁可以拥有多个condition对象(条件队列),singal和signalAll可以选择唤醒不同的队列中的线程;而同一个synchronized块只能有一个监视器对象,一个条件队列。
Lock可以提高多个线程进行读操作的效率,非常灵活。(可以通过readwritelock实现读写分离)。
synchronized使用Thread.holdLock(监视器对象)检测当前线程是否持有锁,Lock可以通过lock.trylock或者isHeldExclusively方法判断是否获取到锁。

不可重入独占锁简单实现

  经过上面的学习,我们知道了AQS的大概设计思路与方法,以及规范的锁需要实现Lock接口,现在我们尝试自己构建一个简单的独占锁。
  顾名思义,独占锁就是在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能
  处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取锁,下面是一个基于AQS的独占锁的实现。
  我们需要重写tryAcquire和tryRelease(int releases)方法。将同步状态state值为1看锁被获取了,使用setExclusiveOwnerThread方法记录获取到锁的线程,state为0看作锁没被获取。另外,下面的简单实现并没有可重入的考虑,因此不具备重入性!
  从实现中能够看出来,有了AQS工具,我们实现自定义同步组件还是比较简单的!

/**
 * @author lx
 */
public class ExclusiveLock implements Lock {
    /**
     * 将AQS的实现组合到锁的实现内部
     * 对于同步状态state,这里的实现将1看成同步,0看成未同步
     */
    private static class Sync extends AbstractQueuedSynchronizer {
        /**
         * 重写isHeldExclusively方法
         *
         * @return 是否处于锁占用状态
         */
        @Override
        protected boolean isHeldExclusively() {
            //state是否等于1
            return getState() == 1;
        }

        /**
         * 重写tryAcquire方法,尝试获取锁
         * 这里的实现为:当state状态为0的时候可以获取锁
         *
         * @param acquires 参数,这里我们没用到
         * @return 获取成功返回true,失败返回false
         */
        @Override
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /**
         * 重写tryRelease方法,释放锁
         * 这里的实现为:当state状态为1的时候,将状态设置为0
         *
         * @param releases 参数,这里我们没用到
         * @return 释放成功返回true,失败返回false
         */
        @Override
        protected boolean tryRelease(int releases) {
            //如果尝试解锁的线程不是加锁的线程,那么抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread()) {
                throw new IllegalMonitorStateException();
            }
            //设置当前拥有独占访问权限的线程为null
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        /**
         * 返回一个Condition,每个condition都包含了一个condition队列
         * 用于实现线程在指定条件队列上的主动等待和唤醒
         *
         * @return 每次调用返回一个新的ConditionObject
         */
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    /**
     * 仅需要将操作代理到Sync实例上即可
     */
    private final Sync sync = new Sync();

    /**
     * lock接口的lock方法
     */
    @Override
    public void lock() {
        sync.acquire(1);
    }

    /**
     * lock接口的tryLock方法
     */
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    /**
     * lock接口的unlock方法
     */
    @Override
    public void unlock() {
        sync.release(1);
    }

    /**
     * lock接口的newCondition方法
     */
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }
}

同步队列

同步队列的结构

public abstract class AbstractQueuedSynchronizer extends
            AbstractOwnableSynchronizer implements java.io.Serializable {
        /**
         * 当前获取锁的线程,该变量定义在父类中,AQS直接继承。在独占锁的获取时,如果是重入锁,那么需要知道到底是哪个线程获得了锁。没有就是null
         */
        private transient Thread exclusiveOwnerThread;
        /**
         * AQS中保持的对同步队列的引用
         * 队列头结点,实际上是一个哨兵结点,不代表任何线程,head所指向的Node的thread属性永远是null。
         */
        private transient volatile Node head;
        /**
         * 队列尾结点,后续的结点都加入到队列尾部
         */
        private transient volatile Node tail;
        /**
         * 同步状态
         */
        private volatile int state;

        /**
         * Node内部类,同步队列的结点类型
         */
        static final class Node {

            /*AQS支持共享模式和独占模式两种类型,下面表示构造的结点类型标记*/
            /**
             * 共享模式下构造的结点,用来标记该线程是获取共享资源时被阻塞挂起后放入AQS 队列的
             */
            static final Node SHARED = new Node();
            /**
             * 独占模式下构造的结点,用来标记该线程是获取独占资源时被阻塞挂起后放入AQS 队列的
             */
            static final Node EXCLUSIVE = null;


            /*线程结点的等待状态,用来表示该线程所处的等待锁的状态*/

            /**
             * 指示当前结点(线程)需要取消等待
             * 由于在同步队列中等待的线程发生等待超时、中断、异常,即放弃获取锁,需要从同步队列中取消等待,就会变成这个状态
             * 如果结点进入该状态,那么不会再变成其他状态
             */
            static final int CANCELLED = 1;
            /**
             * 指示当前结点(线程)的后续结点(线程)需要取消等待(被唤醒)
             * 如果一个结点状态被设置为SIGNAL,那么后继结点的线程处于挂起或者即将挂起的状态
             * 当前结点的线程如果释放了锁或者放弃获取锁并且结点状态为SIGNAL,那么将会尝试唤醒后继结点的线程以运行
             * 这个状态通常是由后继结点给前驱结点设置的。一个结点的线程将被挂起时,会尝试设置前驱结点的状态为SIGNAL
             */
            static final int SIGNAL = -1;
            /**
             * 线程在等待队列里面等待,waitStatus值表示线程正在等待条件
             * 原本结点在等待队列中,结点线程等待在Condition上,当其他线程对Condition调用了signal()方法之后
             * 该结点会从从等待队列中转移到同步队列中,进行同步状态的获取
             */
            static final int CONDITION = -2;
            /**
             * 释放共享资源时需要通知其他结点,waitStatus值表示下一个共享式同步状态的获取应该无条件传播下去
             */
            static final int PROPAGATE = -3;
            /**
             * 记录当前线程等待状态值,包括以上4中的状态,还有0,表示初始化状态
             */
            volatile int waitStatus;

            /**
             * 前驱结点,当结点加入同步队列将会被设置前驱结点信息
             */
            volatile Node prev;

            /**
             * 后继结点
             */
            volatile Node next;

            /**
             * 当前获取到同步状态的线程
             */
            volatile Thread thread;

            /**
             * 等待队列中的后继结点,如果当前结点是共享模式的,那么这个字段是一个SHARED常量
             * 在独占锁模式下永远为null,仅仅起到一个标记作用,没有实际意义。
             */
            Node nextWaiter;

            /**
             * 如果是共享模式下等待,那么返回true(因为上面的Node nextWaiter字段在共享模式下是一个SHARED常量)
             */
            final boolean isShared() {
                return nextWaiter == SHARED;
            }

            /**
             * 用于建立初始头结点或SHARED标记
             */
            Node() {
            }

            /**
             * 用于添加到等待队列
             *
             * @param thread
             * @param mode
             */
            Node(Thread thread, Node mode) {
                this.nextWaiter = mode;
                this.thread = thread;
            }
            //......
        }
    }
}

 由上面的源码可知,同步队列的基本结构如下图:

image.png

在AQS内部Node的源码中我们能看到,同步队列是"CLH" (Craig, Landin, andHagersten) 锁队列的变体,它的head引用指向的头结点作为哨兵结点,不存储任何与等待线程相关的信息,或者可以看成已经获得锁的结点。第二个结点开始才是真正的等待线程构建的结点,后续的结点会加入到链表尾部。
  将新结点添加到链表尾部的方法是compareAndSetTail(Node expect,Node update)方法,该方法是一个CAS方法,能够保证线程安全。
  最终获取锁的线程所在的结点,会被设置成为头结点(setHead方法),该设置步骤是通过获取锁成功的线程来完成的,由于只有一个线程能够成功获取到锁,因此设置的方法并不需要使用CAS来保证。
  同步队列遵循先进先出(FIFO),头结点的next结点是将要获取到锁的结点,线程在释放锁的时候将会唤醒后继结点,然后后继结点会尝试获取锁。

锁的获取与释放

  Lock中“锁”的状态使用state变量来表示,一般来说0表示锁没被占用,大于0表示所已经被占用了。
  AQS提供的锁的获取和释放分为独占式的和共享式的:

独占式:顾名思义就是同一时刻只能有一个线程获取到锁,其他获取锁线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取到锁。
共享式:同一时刻能够有多个线程获取到锁。
  对于AQS 来说,线程同步的关键是对同步状态state的操作:

在独占式下获取和释放锁使用的方法为: void acquire( int arg) 、void acquirelnterruptibly(int arg) 、boolean release( int arg)。
在共享式下获取和释放锁的方法为: void acquireShared(int arg) 、void acquireSharedInterruptibly(int arg)、 boolean reaseShared(int arg)。
  获取锁的大概通用流程如下:
  线程会首先尝试获取锁,如果失败,则将当前线程以及等待状态等信息包成一个Node结点加到同步队列里。接着会不断循环尝试获取锁(获取锁的条件是当前结点为head的直接后继才会尝试),如果失败则会尝试阻塞自己(阻塞的条件是当前节结点的前驱结点是SIGNAL状态),阻塞后将不会执行后续代码,直至被唤醒;当持有锁的线程释放锁时,会唤醒队列中的后继线程,或者阻塞的线程被中断或者时间到了,那么阻塞的线程也会被唤醒。

  如果分独占式和共享式,那么在上面的通用步骤之下有这些区别:

独占式获取的锁是与具体线程绑定的,就是说如果一个线程获取到了锁,exclusiveOwnerThread字段就会记录这个线程,其他线程再尝试操作state 获取锁时会发现当前该锁不是自己持有的,就会在获取失败后被放入AQS 同步队列。比如独占锁ReentrantLock 的实现, 当一个线程获取了ReentrantLock 的锁后,在AQS 内部会首先使用CAS操作把state 状态值从0变为1 ,然后设置当前锁的持有者为当前线程,当该线程再次获取锁时发现它就是锁的持有者,则会把状态值从1变为2,也就是设置可重入次数,而当另外一个线程获取锁时发现自己并不是该锁的持有者就会被放入AQS 同步队列后挂起。
共享式获取的锁与具体线程是不相关的,当多个线程去请求锁时通过CAS 方式竞争获取锁,当一个线程获取到了锁后,另外一个线程再次去获取时如果当前锁还能满足它的需要,则当前线程只需要使用CAS 方式进行获取即可。比如Semaphore 信号量, 当一个线程通过acquire()方法获取信号量时,会首先看当前信号量个数是否满足需要,不满足则把当前线程放入同步队列,如果满足则通过自旋CAS 获取信号量,相应的信号量个数减少对应的值。
  实际上,具体的步骤更加复杂,下面讲解源码的时候会提到!

acquire独占式获取锁

  通过调用AQS的acquire模版方法可以独占式的获取锁,该方法不会响应中断,也就是由于线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出。基于独占式实现的组件有ReentrantLock等。
  该方法大概步骤如下:

首先调用tryAcquire方法尝试获取锁,如果获取锁成功会返回true,方法结束;否则获取锁失败返回false,然后进行下一步的操作。
通过addWaiter方法将线程按照独占模式Node.EXCLUSIVE构造同步结点,并添加到同步队列的尾部。
然后通过acquireQueued(Node node,int arg)方法继续自旋获取锁。
一次自旋中如果获取不到锁,那么判断是否可以挂起并尝试挂起结点中的线程(调用LockSupport.park(this)方法挂起自己,注意这里的线程状态是WAITING)。而挂起线程的唤醒主要依靠前驱结点或线程被中断来实现,注意唤醒之后会继续自旋尝试获得锁。
最终只有获得锁的线程才能从acquireQueued方法返回,然后根据返回值判断是否调用selfInterrupt设置中断标志位,但此时线程处于运行态,即使设置中断标志位也不会抛出异常(即acquire(lock)方法不会响应中断)。
线程获得锁,acquire方法结束,从lock方法中返回,继续后续执行同步代码!

/**
 * 独占式的尝试获取锁,一直获取不成功就进入同步队列等待
 */
public final void acquire(int arg) {
    //内部是由4个方法的调用组成的
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire尝试获取独占锁

  熟悉的tryAcquire方法,这个方法我们在最开头讲“AQS的设计”时就提到过,该方法是AQS的子类即我们自己实现的,用于首次尝试获取独占锁,一般来说就是对state的改变、或者重入锁的检查、设置当前获得锁的线程等等,不同的锁有自己相应的逻辑判断,这里不多讲,后面讲具体锁的实现的时候(比如ReentrantLock)会讲到。总之,获取成功该方法就返回true,失败就返回false。
  在AQS的中tryAcquire的实现为抛出异常,因此需要子类重写:

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

addWaiter加入到同步队列

  addWaiter方法是AQS提供的,也不需要我们重写,或者说是锁的通用方法!
  addWaiter方法用于将按照独占模式构造的同步结点Node.EXCLUSIVE添加到同步队列的尾部。大概步骤为:

按照给定模式,构建新结点。
如果同步队列不为null,则尝试将新结点添加到队列尾部(只尝试一次),如果添加成功则返回新结点,方法结束。
如果队列为null或者添加失败,则调用enq方法循环尝试添加,直到成功,返回新结点,方法结束。

/**
 * addWaiter(Node node)方法将获取锁失败的线程构造成结点加入到同步队列的尾部
 *
 * @param mode 模式。独占模式传入的是一个Node.EXCLUSIVE,即null;共享模式传入的是一个Node.SHARED,即一个静态结点对象(共享的、同一个)
 * @return 返回构造的结点
 */
private Node addWaiter(Node mode) {
    /*1 首先构造结点*/
    Node node = new Node(Thread.currentThread(), mode);
    /*2 尝试将结点直接放在队尾*/
    //直接获取同步器的tail结点,使用pred来保存
    Node pred = tail;
    /*如果pred不为null,实际上就是队列不为null
     * 那么使用CAS方式将当前结点设为尾结点
     * */
    if (pred != null) {
        node.prev = pred;
        //通过使用compareAndSetTail的CAS方法来确保结点能够被线程安全的添加,虽然不一定能成功。
        if (compareAndSetTail(pred, node)) {
            //将新构造的结点置为原队尾结点的后继
            pred.next = node;
            //返回新结点
            return node;
        }
    }
    /*
     * 3 走到这里,可能是:
     * (1) 由于可能是并发条件,并且上面的CAS操作并没有循环尝试,因此可能添加失败
     * (2) 队列可能为null
     * 调用enq方法,采用自旋方式保证构造的新结点成功添加到同步队列中
     * */
    enq(node);
    return node;
}

/**
 * addWaiter方法中使用到的Node构造器
 *
 * @param thread 当前线程
 * @param mode   模式
 */
Node(Thread thread, AbstractQueuedSynchronizer.Node mode) {
    //等待队列中的后继结点 就等于该结点的模式
    //由此可知,共享模式该值为Node.SHARED结点常量,独占模式该值为null
    this.nextWaiter = mode;
    //当前线程
    this.thread = thread;
}
enq保证结点入队

  enq方法用在同步队列为null或者一次CAS添加失败的时候,enq要保证结点最终必定添加成功。大概步骤为:

开启一个死循环,在死循环中进行如下操作;
如果队列为空,那么初始化队列,添加一个哨兵结点,结束本次循环,继续下一次循环;
如果队列不为空,那么向前面的方法一样,则尝试将新结点添加到队列尾部,如果添加成功则返回新结点的前驱,循环结束;如果不成功,结束本次循环,继续下一次循环。
  enq方法返回的是新结点的前驱,当然在addWaiter方法中没有用到。
  另外,添加头结点使用的compareAndSetHead方法和添加尾结点使用的compareAndSetTail方法都是CAS方法,并且都是调用Unsafe类中的本地方法,因为线程挂机、恢复、CAS操作等最终会通过操作系统中实现,Unsafe类就提供了Java与底层操作系统进行交互的直接接口,这个类的里面的许多操作类似于C的指针操作,通过找到对某个属性的偏移量,直接对该属性赋值,因为与Java本地方法对接都是Hospot源码中的方法,而这些的方法都是采用C++写的,必须使用指针!
  也可以说Unsafe是AQS的实现并发控制机制基石。因此在学习AQS的时候,可以先了解Unsafe:Java中的Unsafe类的原理详解与使用案例。

/**
 * 循环,直到尾结点添加成功
 */
private Node enq(final Node node) {
    /*死循环操作,直到添加成功*/
    for (; ; ) {
        //获取尾结点t
        Node t = tail;
        /*如果队列为null,则初始化同步队列*/
        if (t == null) {
            /*调用compareAndSetHead方法,初始化同步队列
             * 注意:这里是新建了一个空白结点,这就是传说中的哨兵结点
             * CAS成功之后,head将指向该哨兵结点,返回true
             * */
            if (compareAndSetHead(new Node()))
                //尾结点指向头结点(哨兵结点)
                tail = head;
            /*之后并没有结束,而是继续循环,此时队列已经不为空了,因此会进行下面的逻辑*/
        }
        /*如果队列不为null,则和外面的的方法类似,调用compareAndSetTail方法,新建新结点到同步队列尾部*/
        else {
            /*1 首先修改新结点前驱的指向,这一步不是安全的
            但是没关系,因为这一步如果发生了冲突,那么下面的CAS操作必然之后有一条线程会成功
            其他线程将会重新循环尝试*/
            node.prev = t;
            /*
             * 2 调用compareAndSetTail方法通过CAS方式尝试将结点添加到同步队列尾部
             * 如果添加成功,那么才能继续下一步,结束这个死循环,否则就会不断循环尝试添加
             * */
            if (compareAndSetTail(t, node)) {
                //3 修改原尾结点后继结点的指向
                t.next = node;
                //返回新结点,结束死循环
                return t;
            }
        }
    }
}

/**
 * CAS添加头结点. 仅仅在enq方法中用到
 *
 * @param update 头结点
 * @return true 成功;false 失败
 */
private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}


/**
 * CAS添加尾结点. 仅仅在enq方法中用到
 *
 * @param expect 预期原尾结点
 * @param update 新尾结点
 * @return true 成功;false 失败
 */
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

在addWaiter和enq方法中,成为尾结点需要三步:

设置前驱prev
设置tail
设置后继next
  由于第二步设置tail是CAS操作,那么只能保证node的前驱prev一定是正确的,但是此后设置后继的操作却不一定能够马上成功就切换到了其他线程,此时next可能为null,但实际他的后继并不一定真的为null。
  因此同步队列只能保证前驱prev一定是可靠的,但是next却不一定可靠,所以后面的源码的遍历操作基本上都是从后向前通过前驱prev进行遍历的。

acquireQueued结点自旋获取锁

  能够走到该方法,那么说明通过了tryAcquire()和addWaiter()方法,表示该线程获取锁已经失败并且被放入同步队列尾部了。
  acquireQueued方法表示结点进入同步队列之后的动作,实际上就进入了一个自旋的过程,自旋过程中,当条件满足,获取到了锁,就可以从这个自旋中退出并返回,否则可能会阻塞该结点的线程,后续即使阻塞被唤醒,还是会自旋尝试获取锁,直到成功或者而抛出异常。
  最终如果该方法会因为获取到锁而退出,则会返回否被中断标志的标志位 或者 因为异常而退出,则会抛出异常!大概步骤为:

同样开启一个死循环,在死循环中进行下面的操作;
如果当前结点的前驱是head结点,那么尝试获取锁,如果获取锁成功,那么当前结点设置为头结点head,当前结点线程出队,表示当前线程已经获取到了锁,然后返回是否被中断标志,结束循环,进入finally;
如果当前结点的前驱不是head结点或者尝试获取锁失败,那么判断当前线程是否应该被挂起,如果返回true,那么调用parkAndCheckInterrupt挂起当前结点的线程(LockSupport.park 方法挂起线程,线程出于WAITING),此时不再执行后续的步骤、代码。
如果当前线程不应该被挂起,即返回false,那本次循环结束,继续下一次循环。
如果线程被其他线程唤醒,那么判断是否是因为中断而被唤醒并修改标志位,同时继续循环,直到在步骤2获得锁,才能跳出循环!(这也是acquire方法不会响应中断的原理—park方法被中断时不会抛出异常,仅仅是从挂起状态返回,然后需要继续尝试获取锁)
最终,线程获得了锁跳出循环,或者发生异常跳出循环,那么会执行finally语句块,finally中判断线程是否是因为发生异常而跳出循环,如果是,那么执行cancelAcquire方法取消该结点获取锁的请求;如果不是,即因为获得锁跳出循环,则finally中什么也不干!

/**
 * @param node 新结点
 * @param arg  参数
 * @return 如果在等待时中断,则返回true
 */
final boolean acquireQueued(final Node node, int arg) {
    //failed表示获取锁是否失败标志
    boolean failed = true;
    try {
        //interrupted表示是否被中断标志
        boolean interrupted = false;
        /*死循环*/
        for (; ; ) {
            //获取新结点的前驱结点
            final Node p = node.predecessor();
            /*只有前驱结点是头结点的时候才能尝试获取锁
             * 同样调用tryAcquire方法获取锁
             * */
            if (p == head && tryAcquire(arg)) {
                //获取到锁之后,就将自己设置为头结点(哨兵结点),线程出队列
                setHead(node);
                //前驱结点(原哨兵结点)的链接置空,由JVM回收
                p.next = null;
                //获取锁是否失败改成false,表示成功获取到了锁
                failed = false;
                //返回interrupted,即返回线程是否被中断
                return interrupted;
            }
            /*前驱结点不是头结点或者获取同步状态失败*/
            /*shouldParkAfterFailedAcquire检测线程是否应该被挂起,如果返回true
             * 则调用parkAndCheckInterrupt用于将线程挂起
             * 否则重新开始循环
             * */
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                /*到这一步,说明是当前结点(线程)因为被中断而唤醒,那就改变自己的中断标志位状态信息为true
                 * 然后又从新开始循环,直到获取到锁,才能返回
                 * */
                interrupted = true;
        }
    }
    /*线程获取到锁或者发生异常之后都会执行的finally语句块*/ finally {
        /*如果failed为true,表示获取锁失败,即对应发生异常的情况,
        这里发生异常的情况只有在tryAcquire方法和predecessor方法中可能会抛出异常,此时还没有获得锁,failed=true
        那么执行cancelAcquire方法,该方法用于取消该线程获取锁的请求,将该结点的线程状态改为CANCELLED,并尝试移除结点(如果是尾结点)
        另外,在超时等待获取锁的的方法中,如果超过时间没有获取到锁,也会调用该方法

        如果failed为false,表示获取到了锁,那么该方法直接结束,继续往下执行;*/
        if (failed)
            //取消获取锁请求,将当前结点从队列中移除,
            cancelAcquire(node);
    }
}


/**
 * 位于Node结点类中的方法
 * 返回上一个结点,或在 null 时引发 NullPointerException。 当前置不能为空时使用。 空检查可以取消,表示此异常无代码层面的意义,但可以帮助 VM?所以这个异常到底有啥用?
 *
 * @return 此结点的前驱
 */
final Node predecessor() throws NullPointerException {
    //获取前驱
    Node p = prev;
    //如果为null,则抛出异常
    if (p == null)
        throw new NullPointerException();
    else
        //返回前驱
        return p;
}


/**
 * head指向node新结点,该方法是在tryAcquire获取锁之后调用,不会产生线程安全问题
 *
 * @param node 新结点
 */
private void setHead(Node node) {
    head = node;
    //新结点的thread和prev属性置空
    //即丢弃原来的头结点,新结点成为哨兵结点,内部线程出队
    //设置里虽然线程引用置空了,但是一般在tryAcquire方法中轨记录获取到锁的线程,因此不担心找不到是哪个线程获取到了锁
    //这里也能看出,哨兵结点或许也可以叫做"获取到锁的结点"
    node.thread = null;
    node.prev = null;
}
shouldParkAfterFailedAcquire结点是否应该挂起

  shouldParkAfterFailedAcquire方法在没有获取到锁之后调用,用于判断当前结点是否需要被挂起。大概步骤如下:

如果前驱结点已经是SIGNAL(-1)状态,即表示当前结点可以挂起,返回true,方法结束;
否则,如果前驱结点状态大于0,即 Node.CANCELLED,表示前驱结点放弃了锁的等待,那么由该前驱向前查找,直到找到一个状态小于等于0的结点,当前结点排在该结点后面,返回false,方法结束;
否则,前驱结点的状态既不是SIGNAL(-1),也不是CANCELLED(1),尝试CAS设置前驱结点的状态为SIGNAL(-1),返回false,方法结束!
  只有前驱结点状态为SIGNAL时,当前结点才能安心挂起,否则一直自旋!
  从这里能看出来,一个结点的SIGNAL状态一般都是由它的后继结点设置的,但是这个状态却是表示后继结点的状态,表示的意思就是前驱结点如果释放了锁,那么就有义务唤醒后继结点!

/**
 * 检测当前结点(线程)是否应该被挂起
 *
 * @param pred 该结点的前驱
 * @param node 该结点
 * @return 如果前驱结点已经是SIGNAL状态,当前结点才能挂起,返回true;否则,可能会查找新的前驱结点或者尝试将前驱结点设置为SIGNAL状态,返回false
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获取 前取的waitStatus_等待状态
    //回顾创建结点时候,并没有给waitStatus赋值,因此每一个结点最开始的时候waitStatus的值都为0
    int ws = pred.waitStatus;
    /*如果前驱结点已经是SIGNAL状态,即表示当前结点可以挂起*/
    if (ws == Node.SIGNAL)
        return true;
    /*如果前驱结点状态大于0,即 Node.CANCELLED 表示前驱结点放弃了锁的等待*/
    if (ws > 0) {
        /*由该前驱向前查找,直到找到一个状态小于等于0的结点(即没有被取消的结点),当前结点成为该结点的后驱,这一步很重要,可能会清理一段被取消了的结点,并且如果该前驱释放了锁,还会唤醒它的后继,保持队列活性*/
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    }
    /*否则,前驱结点的状态既不是SIGNAL(-1),也不是CANCELLED(1)*/
    else {
        /*前驱结点的状态CAS设置为SIGNAL(-1),可能失败,但没关系,因为失败之后会一直循环*/
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    //返回false,表示当前结点不能挂起
    return false;
}
parkAndCheckInterrupt挂起线程&判断中断状态

  shouldParkAfterFailedAcquire方法返回true之后,将会调用parkAndCheckInterrupt方法挂起线程并且后续判断中断状态,分两步:

使用LockSupport.park(this)挂起该线程,不再执行后续的步骤、代码。直到该线程被中断或者被唤醒(unpark)!
如果该线程被中断或者唤醒,那么返回Thread.interrupted()方法的返回值,该方法用于判断前线程的中断状态,并且清除该中断状态,即,如果该线程因为被中断而唤醒,则中断状态为true,将中断状态重置为false,并返回true,如果该线程不是因为中断被唤醒,则中断状态为false,并返回false。

/**
 * 挂起线程,在线程返回后返回中断状态
 *
 * @return 如果因为线程中断而返回,而返回true,否则返回false
 */
private final boolean parkAndCheckInterrupt() {
    /*1)使用LockSupport.park(this)挂起该线程,不再执行后续的步骤、代码。直到该线程被中断或者被唤醒(unpark)*/
    LockSupport.park(this);
    /*2)如果该线程被中断或者唤醒,那么返回Thread.interrupted()方法的返回值,
    该方法用于判断前线程的中断状态,并且清除该中断状态,即,如果该线程因为被中断而唤醒,则中断状态为true,将中断状态重置为false,并返回true,注意park方法被中断时不会抛出异常!
    如果该线程不是因为中断被唤醒,则中断状态为false,并返回false*/
    return Thread.interrupted();
}
finally代码块

  在acquireQueued方法中,具有一个finally代码块,那么无论try中发生了什么,finally代码块都会执行的。在acquire独占式不可中断获取锁的方法中,执行finally的只有两种情况:

当前结点(线程)最终获取到了锁,此时会进入finally,而在获取到锁之后会设置failed = false。
在try中发生了异常,此时直接跳到finally中。这里发生异常的情况只可能在tryAcquire或predecessor方法中发生,然后直接进入finally代码块中,此时还没有获得锁,failed=true!
a) tryAcquire方法是我们自己实现的,抛出什么异常由我们来定,就算抛出异常一般也不会在acquireQueued中抛出,可能在最开始调用tryAcquire时就抛出了。
b) predecessor方法中,会检查如果前驱结点为null则抛出NullPointerException。但是注释中又说这个检查无代码层面的意义,或许是这个异常永远不会抛出?
  finally代码块中的逻辑为:

如果failed = true,表示没有获取锁而进行finally,即发生了异常。那么执行cancelAcquire方法取消当前结点线程获取锁的请求,acquireQueued方法结束,然后抛出异常。
如果failed = false,表示已经获取到了锁,那么实际上finally中什么都不会执行。acquireQueued方法结束,返回interrupted—是否被中断标志。
  综上所述,在acquire独占式不可中断获取锁的方法中,大部分情况在finally中都是什么也不干就返回了,或者说抛出异常的情况基本没有,因此cancelAcquire方法基本不考虑。
  但是在可中断获取锁或者超时获取锁的方法中,执行到cancelAcquire方法的情况还是比较常见的。因此将cancelAcquire方法的源码分析放到可中断获取锁方法的源码分析部分!

selfInterrupt安全中断

  selfInterrupt是acquire中最后可能调用的一个方法,顾名思义,用于安全的中断,什么意思呢,就是根据!tryAcquire和acquireQueued返回值判断是否需要设置中断标志位。
  只有tryAcquire尝试失败,并且acquireQueued方法true时,才表示该线程是被中断过了的,但是在parkAndCheckInterrupt里面判断中断标志位之后又重置的中断标志位(interrupted方法会重置中断标志位)。
  虽然看起来没啥用,但是本着负责的态度,还是将中断标志位记录下来。那么此时重新设置该线程的中断标志位为true。

/**
 * 中断当前线程,由于此时当前线程出于运行态,因此只会设置中断标志位,并不会抛出异常
 */
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

release独占式锁释放

  当前线程获取到锁并执行了相应逻辑之后,就需要释放锁,使得后续结点能够继续获取锁。通过调用AQS的release(int arg)模版方法可以独占式的释放锁,在该方法大概步骤如下:

尝试使用tryRelease(arg)释放锁,该方法在最开始我们就讲过,是自己实现的方法,通常来说就是将state值为0或者减少、清除当前获得锁的线程等等,如果符合自己的逻辑,锁释放成功则返回true,否则返回false;
如果tryRelease释放成功返回true,判断如果head不为null且head的状态不为0,那么尝试调用unparkSuccessor方法唤醒头结点之后的一个非取消状态(非CANCELLED状态)的后继结点,让其可以进行锁获取。返回true,方法结束;
如果tryRelease释放失败,那么返回false,方法结束。

/**
 * 独占式的释放同步状态
 *
 * @param arg 参数
 * @return 释放成功返回true, 否则返回false
 */
public final boolean release(int arg) {
    /*tryRelease释放同步状态,该方法是自己重写实现的方法
    释放成功将返回true,否则返回false或者自己实现的逻辑*/
    if (tryRelease(arg)) {
        //获取头结点
        Node h = head;
        //如果头结点不为null并且状态不等于0
        if (h != null && h.waitStatus != 0)
            /*那么唤醒头结点的一个出于等待锁状态的后继结点
             * 该方法在acquire中已经讲过了
             * */
            unparkSuccessor(h);
        return true;
    }
    return false;
}

unparkSuccessor唤醒后继结点

  unparkSuccessor用于唤醒参数结点的某个非取消的后继结点,该方法在很多地方法都被调用,大概步骤:

如果当前结点的状态小于0,那么CAS设置为0,表示后继结点可以继续尝试获取锁。
如果当前结点的后继s为null或者状态为取消CANCELLED,则将s先指向null;然后从tail开始到node之间倒序向前查找,找到离tail最近的非取消结点赋给s。需要从后向前遍历,因为同步队列只保证结点前驱关系的正确性。
如果s不为null,那么状态肯定不是取消CANCELLED,则直接唤醒s的线程,调用LockSupport.unpark方法唤醒,被唤醒的结点将从被park的位置继续执行!

/**
 * 唤醒指定结点的后继结点
 *
 * @param node 指定结点
 */
private void unparkSuccessor(Node node) {

    int ws = node.waitStatus;
    /*
     * 1)  如果当前结点的状态小于0,那么CAS设置为0,表示后继结点线程可以先尝试获锁,而不是直接挂起。
     * */
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    //先获取node的直接后继
    Node s = node.next;
    /*
     * 2)  如果s为null或者状态为取消CANCELLED,则从tail开始到node之间倒序向前查找,找到离tail最近的非取消结点赋给s。
     * */
    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;
    }
    /*
     * 3)如果s不为null,那么状态肯定不是取消CANCELLED,则直接唤醒s的线程,调用LockSupport.unpark方法唤醒,被唤醒的结点将从被park的位置向后执行!
     * */
    if (s != null)
        LockSupport.unpark(s.thread);
}

acquirelnterruptibly独占式可中断获取锁

  在JDK1.5之前,当一个线程获取不到锁而被阻塞在synchronized之外时,如果对该线程进行中断操作,此时该线程的中断标志位会被修改,但线程依旧会阻塞在synchronized上,等待着获取锁,即无法响应中断。
  上面分析的独占式获取锁的方法acquire,同样是不会响应中断的。但是AQS提供了另外一个acquireInterruptibly模版方法,调用该方法的线程在等待获取锁时,如果当前线程被中断,会立刻返回,并抛出InterruptedException。

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    //如果当前线程被中断,直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //尝试获取锁
    if (!tryAcquire(arg))
        //如果没获取到,那么调用AQS 可被中断的方法
        doAcquireInterruptibly(arg);
}

doAcquireInterruptibly独占式可中断获取锁

  doAcquireInterruptibly会首先判断线程是否是中断状态,如果是则直接返回并抛出异常其他不步骤和独占式不可中断获取锁基本原理一致,还有一点的区别就是在后续挂起的线程因为线程被中断而返回时的处理方式不一样:独占式不可中断获取锁仅仅是记录该状态,interrupted = true,紧接着又继续循环获取锁;独占式可中断获取锁则直接抛出异常,因此会直接跳出循环去执行finally代码块。