AQS源码解析

解读源码本来就是一件极其枯燥乏味的事情 希望你坚持住 过了这道坎 你会看到不一样的风景;

 

在解读源码之前我们来讨论一下源码解读的技巧:

  • 跑不起来的源码不读
  • 解决问题就好-目的性
  • 一条线索到底
  • 无关细节略过

1、跑不起来的源码不读

因为大部分源码都会用到好多的设计模式,这就促使如果源码跑不起来,单纯的看是很难看的懂,如果可以跑起来你就可以Ctrl鼠标单击方法跟进去,这样就会事半功倍;

2、解决问题就好-目的性

在实际工作中你可能会接手一个改过不知道多少遍的代码,这时你就要搞清楚,如果单纯的只是为解决问题,就没有必要去看细节;

3、一条线索到底

读源码一定要一条线跟到底,不要只是都表面,我们直到当一个程序跑起来,可能会很大,很多方法点进去还会调用其他的方法,你不用每个方法都看一遍再进去找,尽量一条线索跟到底,就读一个方法,由浅入深再看一遍;

4、无关细节略过

有些边缘性的东西,在你读第一遍的时候,没有必要的时候,可以略过;

接着上一篇的ReentrantLock,它的内部实现的核心是 CAS操作+volatile;

下面我们就来一步一步解读源码:


一、准备工作(需要了解 ReentrantLock 用法,ReentrantLock的构造方法, 类之间的关系等等)

1、一般的使用方法以及构造方法:

一般使用方法:

final Lock lock = new ReentrantLock();
lock.lock();

构造方法(因为有排它锁和公平锁的概念,构造方法分为默认构造方法和有参构造方法):

 /**
     * 默认构造方法
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    /**
     * 可以传参数 控制锁的公平性
     * @param fair
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

 

2、下面看一下ReentrantLock类:  

ReentrantLock这个类有三个内部类:Sync、NonfairSync、FairSync,他们之间存在了一系列的关系

因为 ReentrantLock 默认为非公平锁,类图就以 NonfairSync为例(公平锁的类图类似):

 

二、源码分析<一>:lock -> acquire -> tryAcquire -> nonfairTryAcquire->返回Boolean类型值

首先你要学会画泳道图,下面先简单看一下泳道图:

下面看一下代码的执行顺序:

2.1、调用Sync类的lock方法:

   /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

2.2、Sync类的lock方法由具体的子类(NonfairSync)来实现:

首先进行  compareAndSetState 操作将state修改为1,并把当前线程设置为这把锁的独占线程;

具体的代码如下:

  /**内部类NonfairSync(非公平锁)继承了Sync*/
        static final class NonfairSync extends java.util.concurrent.locks.ReentrantLock.Sync {
            /**
             * 尝试着直接去修改state 修改成功证明当前没有线程持有这把锁 然后设置当前独占访问的线程
             * 如果修改失败
             */
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                acquire(1);
            }
        }

2.3、AQS.compareAndSetState(0, 1)

 //执行CAS操作 使用了unsafe类的原子操作 多线程的情况下只能有一个线程修改成功
        protected final boolean compareAndSetState(int expect, int update) {
            // See below for intrinsics setup to support this
            return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
        }

2.4、AQS.acquire(1)     (当compareAndSetState修改当前线程独占失败)

/**
         * 这里是先执行tryAcquire(arg)方法,也就是尝试获取锁的功能:
         * 1.如果返回tryAcquire(arg)为true说明获取锁成功,也就是!tryAcquire(arg)为false,
         * 则不会执行后面的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法了,直接执行业务代码去了
         * 
         * 2.如果返回tryAcquire(arg)为false说明获取锁失败,也就是!tryAcquire(arg)为true,
         * 则会再去执行后面的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法
         * 
         * 所以我们下面要分析下tryAcquire(arg)方法做了什么
         * @param arg
         */
        public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }

2.5、NonfairSync.tryAcquire(arg)   尝试获取锁

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

2.6、Sync.nonfairTryAcquire(acquires)

final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                //获取当前同步锁的状态,判断state如果等于0 说明当前没有线程独占这把锁
                int c = getState();
                if (c == 0) {
                    //CAS操作修改state = 1 并设置为当前线程独占 返回true
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                //因为这里有锁重入的概念 所以判断当前持有这把锁的线程和current是不是同一个线程
                else if (current == getExclusiveOwnerThread()) {
                    //如果是同一个线程 state+1
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
        }

 

三、源码分析<二>:tryAcquire-> addWaiter-> acquireQueued

解读AQS源码之前你需要了解AQS的核心是CAS+volatile,这里所谓的CAS是因为在多线程的情况下,没有使用synchronized来同步而是使用了大量的CAS操作(例如线程节点获取锁和加到尾节点上都使用了CAS操作),内部还维护了一个双向链表有头节点和尾节点,定义了一个  private volatile int state 具体的含义由子类自己决定,ReentrantLock中state的含义是:当state等于0时证明当前锁没有任何的线程占有,state=1时,证明有线程占有这把锁,记住这里因为ReentrantLock具有可重入性,所以重入一次state加一,当state减少至0时,释放锁。

  • 利用CAS操作替换synchronized锁整条链表:

  • 内部维护了一条双向链表,有头节点和尾节点,新进来的线程节点利用CAS操作获取当前锁,如果获取失败就会将当前线程节点加到等待队列,这里也使用了CAS操作,尽管此过程没有加锁,但是CAS操作时原子操作保证了原子性,同时使用volatile修饰了state,保证了线程获取同步状态的可见性;

 

首先也简单画一个流程图:

 

 3.1、AQS.addWaiter(Node.EXCLUSIVE)方法 如果tryAcquire的返回值为true获取同步锁成功否则获取同步锁失败) 当返回false执行addWaiter方法:

  • 看addWaiter代码之前先看一下Node类(AQS的内部类):
static final class Node {
        //定义了一个node节点
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled  waitStatus值表示线程已被取消*/
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking waitStatus值,表示后续线程需要解锁*/
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition  waitStatus值表示线程正在等待状态*/
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate  状态需要向后传播
         */
        static final int PROPAGATE = -3;
        //构造方法
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        //头节点
        private transient volatile Node head;
        //尾节点
        private transient volatile Node tail;
        /**
         * The synchronization state. 锁的同步状态
         */
        private volatile int state;

        volatile int waitStatus;
        //前一个node节点
        volatile Node prev;
        //后一个node节点
        volatile Node next;
        //线程
        volatile Thread thread;
        //后面等待的节点
        Node nextWaiter;
    }
  • 接下来看一下 AQS.addWaiter(Node.EXCLUSIVE), arg)从方法名可以看出添加一个排它的等待者):
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;
                //使用CAS操作 将等待队列的双向链表 当前节点设置为尾部
                if (compareAndSetTail(pred, node)) {
                    //之前的尾部的下一个节点指向新加入的节点
                    pred.next = node;
                    //返回节点
                    return node;
                }
            }
            //如果队列没有尾节点
            enq(node);
            return node;
        }

3.2、AQS.enq(node) 方法  如果尾节点为空,执行完整的添加节点操作:

/**
     * 执行完整的添加节点操作
     * @param node
     * @return
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //判断为节点是否为空
            if (t == null) { // Must initialize
                //CAS操作把当前线程节点设置为头节点
                if (compareAndSetHead(new Node()))
                    //此时头节点和尾节点都为当前节点
                    tail = head;
            } else {
                //如果此时已经有别的节点加入 CAS操作 把当前节点设置为尾节点
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

3.3、AQS.acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

首先获得当前node的前驱节点,判断前驱节点是否为head;调用  tryAcquire  方法获取同步锁,成功了就将当前节点设置为头节点,如果前驱节点不为头节点或者获取同步锁失败,需要调用  shouldParkAfterFailedAcquire方法 ,通过内部的 waitStatus 的值来判断是否需要阻塞当前线程。

 //以排他不可中断模式获取线程队列
    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;
                }
                //获取同步状态失败 判断是否需要阻塞线程,内部通过状态判断,waitStatus为-1返回true
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    //线程进入阻塞状态
                    interrupted = true;
            }
        } finally {
            if (failed)
                //取消正在进行的获取尝试
                cancelAcquire(node);
        }
    }

3.4、通过内部的 waitStatus 的值来判断是否需要阻塞当前线程;  

  • waitStatus = -1时  返回true ,当前线程需要阻塞 ,调用 parkAndCheckInterrupt  -> LockSupport.park(this);
  • waitStatus =  1时  表示前置节点已经等待超时或者已经被中断了 ,这时需要将其从队列中删除;  
  • waitStatus = -2时  表示当前节点在condition(同步队列)中等待;
  • waitStatus = -3时  表示状态需要向后传播,尽在共享锁的条件下使用;
/**
     * 通过内部waitStatus 的状态来判断线程是否需要阻塞
     * @param pred 前驱节点
     * @param node 当前节点
     * @return 返回 boolean 类型值
     */
    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;
            //CANCELLED =  1; 表示前驱节点被取消了  跳过前一个节点重试
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // CONDITION = -2   CONDITION = -3 设置当前 node 的前驱节点的waitStatus = -1(SIGNAL)
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

 

总结:AQS源码主要核心在于CAS操作和volatile  利用CAS操作代替了Synchronized锁整条链表的操作 ,volatile 修饰的state(锁的状态)下维护着一个双向链表;

posted @ 2020-07-28 01:27  AmourLee  阅读(252)  评论(0编辑  收藏  举报