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(锁的状态)下维护着一个双向链表;