首页 > 其他 > 详细

AQS源码解析

时间:2020-07-28 10:33:11      阅读:54      评论:0      收藏:0      [点我收藏+]

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

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

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

1、跑不起来的源码不读

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

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

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

3、一条线索到底

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

4、无关细节略过

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

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

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


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

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

一般使用方法:

final Lock lock = new ReentrantLock();

构造方法:

public ReentrantLock() {
        sync = 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  AQS(AbstractQueuedSynchronizer):

 //执行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 (当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

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

2.6、Sync.nonfairTryAcquire

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操作获取当前锁,或者加到尾节点

技术分享图片

 

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

技术分享图片

 

 3.1、AQS.addWaiter方法 (如果tryAcquire的返回值为false证明直接设置当前线程为独占锁失败了)

  • 看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方法 :尾节点(tail)不为空
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方法  如果尾节点为空,执行完整的添加节点操作

/**
     * 执行完整的添加节点操作
     * @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 

 //以排他不可中断模式获取已经进入的线程队列
    //由条件等待方法和获取方法使用
    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);
        }
    }

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

AQS源码解析

原文:https://www.cnblogs.com/dongl961230/p/13373137.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!