AQS实现原理分析——ReentrantLock

在Java并发包java.util.concurrent中能够看到,很多源码是基于AbstractQueuedSynchronizer(如下简写AQS)这个抽象类,由于它是Java并发包的基础工具类,是实现ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。 
AQS的主要使用方式是继承,子类经过继承AQS并实现它的抽象方法来管理同步状态,在抽象方法中免不了要对同步状态进行更改,这时就须要使用AQS提供的3个方法(getState()、setState(int newState)和compareAndSetState(int expect,int update) 来进行操做,由于他们可以保证状态的改变是安全的 。java

 

1 AQS原理介绍

在 AQS 内部,经过维护一个FIFO队列来管理多线程的排队工做。在公平竞争的状况下,没法获取同步状态的线程将会被封装成一个节点,置于队列尾部。入队的线程将会经过自旋的方式获取同步状态,若在有限次的尝试后,仍未获取成功,线程则会被阻塞住。大体示意图以下: 
此处输入图片的描述 
当头结点释放同步状态后,且后继节点对应的线程被阻塞,此时头结点线程将会去唤醒后继节点线程。后继节点线程恢复运行并获取同步状态后,会将旧的头结点从队列中移除,并将本身设为头结点。大体示意图以下: 
此处输入图片的描述node

 

2 AQS结构

 

2.1 AQS属性

先来看看 AQS 有哪些变量,搞清楚这些基本就知道 AQS 是什么套路了,毕竟能够猜嘛!安全

 
  1. // 头结点,你直接把它当作 当前持有锁的线程 多是最好理解的
  2. private transient volatile Node head;
  3. // 阻塞的尾节点,每一个新的节点进来,都插入到最后,也就造成了一个隐视的链表
  4. private transient volatile Node tail;
  5. // 这个是最重要的,不过也是最简单的,表明当前锁的状态,0表明没有被占用,大于0表明有线程持有当前锁
  6. // 之因此说大于0,而不是等于1,是由于锁能够重入嘛,每次重入都加上1
  7. private volatile int state;
  8. // 表明当前持有独占锁的线程,举个最重要的使用例子,由于锁能够重入
  9. // reentrantLock.lock()能够嵌套调用屡次,因此每次用这个来判断当前线程是否已经拥有了锁
  10. // if (currentThread == getExclusiveOwnerThread()) {state++}
  11. private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer

在并发的状况下,AQS 会将未获取同步状态的线程将会封装成节点,并将其放入同步队列尾部。同步队列中的节点除了要保存线程,还要保存等待状态。无论是独占式仍是共享式,在获取状态失败时都会用到节点类。因此这里咱们要先看一下节点类的实现,为后面的源码分析进行简单铺垫。既然AQS经过一个双向链表来维护全部的的节点,那么先看一下每个节点的结构:数据结构

 
  1. static final class Node {
  2. /** Marker to indicate a node is waiting in shared mode */
  3. // 标识节点当前在共享模式下
  4. static final Node SHARED = new Node();
  5. /** Marker to indicate a node is waiting in exclusive mode */
  6. // 标识节点当前在独占模式下
  7. static final Node EXCLUSIVE = null;
  8. // ======== 下面的几个int常量是给waitStatus用的 ===========
  9. /** waitStatus value to indicate thread has cancelled */
  10. // 代码此线程取消了争抢这个锁
  11. static final int CANCELLED = 1;
  12. /** waitStatus value to indicate successor's thread needs unparking */
  13. // 官方的描述是,其表示当前node的后继节点对应的线程须要被唤醒
  14. static final int SIGNAL = -1;
  15. /** waitStatus value to indicate thread is waiting on condition */
  16. // 本文不分析condition,因此略过吧,下一篇文章会介绍这个
  17. static final int CONDITION = -2;
  18. /**
  19. * waitStatus value to indicate the next acquireShared should
  20. * unconditionally propagate
  21. */
  22. // 一样的不分析,略过吧
  23. static final int PROPAGATE = -3;
  24. // =====================================================
  25. // 取值为上面的一、-一、-二、-3,或者0(之后会讲到)
  26. // 这么理解,暂时只须要知道若是这个值 大于0 表明此线程取消了等待,
  27. // 也许就是说半天抢不到锁,不抢了,ReentrantLock是能够指定timeouot的。。。
  28. volatile int waitStatus;
  29. // 前驱节点的引用
  30. volatile Node prev;
  31. // 后继节点的引用
  32. volatile Node next;
  33. // 这个就是线程本尊
  34. volatile Thread thread;
  35. }

Node 的数据结构其实也挺简单的,就是 thread + waitStatus + pre + next 四个属性而已。多线程

 

2.1 AQS方法

本节将介绍三组重要的方法,经过使用这三组方法便可实现一个同步组件。并发

第一组方法是用于访问/设置同步状态的,以下:app

方法 描述
int getState() 获取同步状态
void setState() 设置同步状态
boolean compareAndSetState(int expect, int update) 经过 CAS 设置同步状态

第二组方须要由同步组件覆写。以下:less

方法 描述
boolean tryAcquire(int arg) 独占式获取同步状态
boolean tryRelease(int arg) 独占式释放同步状态
int tryAcquireShared(int arg) 共享式获取同步状态
boolean tryReleaseShared(int arg) 共享式私房同步状态
boolean isHeldExclusively() 检测当前线程是否获取独占锁

第三组方法是一组模板方法,同步组件可直接调用。以下:ide

方法 描述
void acquire(int arg) 独占式获取同步状态,该方法将会调用 tryAcquire 尝试获取同步状态。获取成功则返回,获取失败,线程进入同步队列等待。
void acquireInterruptibly(int arg) 响应中断版的 acquire
boolean tryAcquireNanos(int arg,long nanos) 超时+响应中断版的 acquire
void acquireShared(int arg) 共享式获取同步状态,同一时刻可能会有多个线程得到同步状态。好比读写锁的读锁就是就是调用这个方法获取同步状态的。
void acquireSharedInterruptibly(int arg) 响应中断版的 acquireShared
boolean tryAcquireSharedNanos(int arg,long nanos) 超时+响应中断版的 acquireShared
boolean release(int arg) 独占式释放同步状态
boolean releaseShared(int arg) 共享式释放同步状态
 

3 ReentrantLock加锁

ReentrantLock 在内部用了内部类 Sync 来管理锁,因此真正的获取锁和释放锁是由 Sync 的实现类来控制的。 
Sync 有两个实现,分别为 NonfairSync(非公平锁)和 FairSync(公平锁),咱们看 NonfairSync 部分。工具

 

3.1 NonfairSync锁

加锁的一步以下:

 
  1. public void lock() {
  2. sync.lock();
  3. }

对于非公平锁,则执行以下流程:

 
  1. final void lock() {
  2. // 若是锁没有被任何线程锁定且加锁成功则设定当前线程为锁的拥有者
  3. if (compareAndSetState(0, 1))
  4. setExclusiveOwnerThread(Thread.currentThread());
  5. else
  6. acquire(1);
  7. }

1)咱们假设在这个时候,尚未任务线程获取锁,这个时候,第一个线程过来了(咱们使用的是非公平锁),那么第一个线程thread1会去获取锁,这时它会调用下面的方法,经过CAS的操做,将当前AQS的state由0变成1,证实当前thread1已经获取到锁,而且将AQS的exclusiveOwnerThread设置成thread1,证实当前持有锁的线程是thread1。 
2)若是此时来了第二个线程thread2,而且咱们假设thread1尚未释放锁,由于咱们使用的是非公平锁,那么thread2首先会进行抢占式的去获取锁,调用NonFairSync.lock方法获取锁。NonFairSync.lock方法的第一个分支是经过CAS操做获取锁,很明显,这一步确定会失败,由于此时thread1尚未释放锁。那么thread2将会走NonFairSync.lock方法的第二个分支,进行acquire(1)操做。acquire(1)实际上是AQS的方法,acquire(1)方法内部首先调用tryAcquire方法,ReentrantLock.NonFairLock重写了tryAcquire方法,而且ReentrantLock.NonFairLock的tryAcquire方法又调用了ReentrantLock.Sync的nonfairTryAcquire方法,nonfairTryAcquire方法以下:

 
  1. // 该方法来自父类AQS,我直接贴过来这边,下面分析的时候一样会这样作,不会给读者带来阅读压力
  2. // 咱们看到,这个方法,若是tryAcquire(arg) 返回true, 也就结束了。
  3. // 不然,acquireQueued方法会将线程压到队列中
  4. public final void acquire(int arg) {
  5. // 首先调用tryAcquire(1)一下,名字上就知道,这个只是试一试
  6. // 由于有可能直接就成功了呢,也就不须要进队列排队了,
  7. // 对于公平锁的语义就是:原本就没人持有锁,根本不必进队列等待(又是挂起,又是等待被唤醒的)
  8. if (!tryAcquire(arg) &&
  9. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  10. selfInterrupt();
  11. }
  12. protected final boolean tryAcquire(int acquires) {
  13. //直接调用下面方法
  14. return nonfairTryAcquire(acquires);
  15. }
  16. /**
  17. * Performs non-fair tryLock. tryAcquire is implemented in
  18. * subclasses, but both need nonfair try for trylock method.
  19. */
  20. // 尝试直接获取锁,返回值是boolean,表明是否获取到锁
  21. // 返回true:1.没有线程在等待锁;2.重入锁,线程原本就持有锁,也就能够理所固然能够直接获取
  22. final boolean nonfairTryAcquire(int acquires) {
  23. final Thread current = Thread.currentThread();
  24. int c = getState();
  25. // state == 0 此时此刻没有线程持有锁
  26. if (c == 0) {
  27. // 用CAS尝试一下,成功了就获取到锁了,
  28. // 不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了 =_=
  29. // 由于刚刚还没人的,我判断过了???
  30. if (compareAndSetState(0, acquires)) {
  31. // 到这里就是获取到锁了,标记一下,告诉你们,如今是我占用了锁
  32. setExclusiveOwnerThread(current);
  33. return true;
  34. }
  35. }
  36. else if (current == getExclusiveOwnerThread()) {
  37. // 会进入这个else if分支,说明是重入了,须要操做:state=state+1
  38. int nextc = c + acquires;
  39. if (nextc < 0) // overflow
  40. throw new Error("Maximum lock count exceeded");
  41. setState(nextc);
  42. return true;
  43. }
  44. // 若是到这里,说明前面的if和else if都没有返回true,说明没有获取到锁
  45. // 回到上面一个外层调用方法继续看:
  46. // if (!tryAcquire(arg)
  47. // && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  48. // selfInterrupt();
  49. return false;
  50. }

nonfairTryAcquire方法的执行逻辑以下: 
1. 获取当前将要去获取锁的线程,在此时的状况下,也就是咱们的thread2线程。 
2. 获取当前AQS的state的值。若是此时state的值是0,那么咱们就经过CAS操做获取锁,而后设置AQS的exclusiveOwnerThread为thread2。很明显,在当前的这个执行状况下,state的值是1不是0,由于咱们的thread1尚未释放锁。 
3. 若是当前将要去获取锁的线程等于此时AQS的exclusiveOwnerThread的线程,则此时将state的值加1,很明显这是重入锁的实现方式。在此时的运行状态下,将要去获取锁的线程不是thread1,也就是说这一步不成立。 
4. 以上操做都不成立的话,咱们直接返回false。 
既然返回了false,那么以后就会调用addWaiter方法,这个方法负责把当前没法获取锁的线程包装为一个Node添加到队尾。经过下面的代码片断咱们就知道调用逻辑:

 
  1. // 假设tryAcquire(arg) 返回false,那么代码将执行:
  2. // acquireQueued(addWaiter(Node.EXCLUSIVE), arg),
  3. // 这个方法,首先须要执行:addWaiter(Node.EXCLUSIVE)
  4. /**
  5. * Creates and enqueues node for current thread and given mode.
  6. *
  7. * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
  8. * @return the new node
  9. */
  10. // 此方法的做用是把线程包装成node,同时进入到队列中
  11. // 参数mode此时是Node.EXCLUSIVE,表明独占模式
  12. private Node addWaiter(Node mode) {
  13. Node node = new Node(Thread.currentThread(), mode);
  14. // Try the fast path of enq; backup to full enq on failure
  15. //如下几行代码想把当前node加到链表的最后面去,也就是进到阻塞队列的最后
  16. Node pred = tail;
  17. // tail!=null => 队列不为空(tail==head的时候,其实队列是空的,不过无论这个吧)
  18. if (pred != null) {
  19. // 设置本身的前驱 为当前的队尾节点
  20. node.prev = pred;
  21. // 用CAS把本身设置为队尾, 若是成功后,tail == node了
  22. if (compareAndSetTail(pred, node)) {
  23. // 进到这里说明设置成功,当前node==tail, 将本身与以前的队尾相连,
  24. // 上面已经有 node.prev = pred
  25. // 加上下面这句,也就实现了和以前的尾节点双向链接了
  26. pred.next = node;
  27. // 线程入队了,能够返回了
  28. return node;
  29. }
  30. // 仔细看看上面的代码,若是会到这里,
  31. // 说明 pred==null(队列是空的) 或者 CAS失败(有线程在竞争入队)
  32. // 读者必定要跟上思路,若是没有跟上,建议先不要往下读了,往回仔细看,不然会浪费时间的
  33. enq(node);
  34. return node;
  35. }

很明显在addWaiter内部: 
第一步:将当前将要去获取锁的线程也就是thread2和独占模式封装为一个node对象。而且咱们也知道在当前的执行环境下,线程阻塞队列是空的,由于thread1获取了锁,thread2也是刚刚来请求锁,因此线程阻塞队列里面是空的。很明显,这个时候队列的尾部tail节点也是null,那么将直接进入到enq方法。 
第二步:咱们首先看下enq方法的内部实现。首先内部是一个自旋循环。

 
  1. /**
  2. * Inserts node into queue, initializing if necessary. See picture above.
  3. * @param node the node to insert
  4. * @return node's predecessor
  5. */
  6. // 采用自旋的方式入队
  7. // 以前说过,到这个方法只有两种可能:等待队列为空,或者有线程竞争入队,
  8. // 自旋在这边的语义是:CAS设置tail过程当中,竞争一次竞争不到,我就屡次竞争,总会排到的
  9. private Node enq(final Node node) {
  10. for (;;) {
  11. Node t = tail;
  12. // 以前说过,队列为空也会进来这里
  13. if (t == null) { // Must initialize
  14. // 初始化head节点
  15. // 细心的读者会知道原来head和tail初始化的时候都是null,反正我不细心
  16. // 仍是一步CAS,你懂的,如今多是不少线程同时进来呢
  17. if (compareAndSetHead(new Node()))
  18. // 给后面用:这个时候head节点的waitStatus==0, 看new Node()构造方法就知道了
  19. // 这个时候有了head,可是tail仍是null,设置一下,
  20. // 把tail指向head,放心,立刻就有线程要来了,到时候tail就要被抢了
  21. // 注意:这里只是设置了tail=head,这里可没return哦,没有return,没有return
  22. // 因此,设置完了之后,继续for循环,下次就到下面的else分支了
  23. tail = head;
  24. } else {
  25. // 下面几行,和上一个方法 addWaiter 是同样的,
  26. // 只是这个套在无限循环里,反正就是将当前线程排到队尾,有线程竞争的话排不上重复排
  27. node.prev = t;
  28. if (compareAndSetTail(t, node)) {
  29. t.next = node;
  30. return t;
  31. }
  32. }
  33. }
  34. }

第一次循环:t为null,随后咱们new出了一个空的node节点,而且经过CAS操做设置了线程的阻塞队列的head节点就是咱们刚才new出来的那个空的node节点,其实这是一个“假节点”,那么什么是“假节点”呢?那就是节点中不包含线程。设置完head节点后,同时又将head节点赋值给尾部tail节点,到此第一次循环结束。此时的节点就是以下: 
此处输入图片的描述 
第二次循环:如今判断维度tail已经不是null了,那么就走第二个分支了,将尾部tail节点赋值给咱们传递进来的节点Node的前驱节点,此时的结构以下: 
此处输入图片的描述 
而后再经过CAS的操做,将咱们传递进来的节点node设置成尾部tail节点,而且将咱们的node节点赋值给原来的老的尾部节点的后继节点,此时的结构以下: 
此处输入图片的描述 
这个时候代码中使用了return关键字,也就是证实咱们通过了2次循环跳出了这个自悬循环体系。

在执行addWaiter将节点加入阻塞队列后,接下来将会调用acquireQueued方法,主要是判断当前节点的前驱节点是否是head节点,若是是的话,就再去尝试获取锁,若是不是,就挂起当前线程。这里可能有人疑问了,为何判断当前节点的前驱节点是head节点的话就去尝试获取锁呢?由于咱们知道head节点是一个假节点,若是当前的节点的前驱节点是头节点便是假节点的话,那么这个假节点的后继节点就有可能有获取锁的机会,因此咱们须要去尝试。 
如今咱们看下acquireQueued方法内部,咱们也能够清楚的看到,这个方法的内部也是一个自悬循环。:

 
  1. // 如今,又回到这段代码了
  2. // if (!tryAcquire(arg)
  3. // && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  4. // selfInterrupt();
  5. // 下面这个方法,参数node,通过addWaiter(Node.EXCLUSIVE),此时已经进入阻塞队列
  6. // 注意一下:若是acquireQueued(addWaiter(Node.EXCLUSIVE), arg))返回true的话,
  7. // 意味着上面这段代码将进入selfInterrupt(),因此正常状况下,下面应该返回false
  8. // 这个方法很是重要,应该说真正的线程挂起,而后被唤醒后去获取锁,都在这个方法里了
  9. /**
  10. * Acquires in exclusive uninterruptible mode for thread already in
  11. * queue. Used by condition wait methods as well as acquire.
  12. *
  13. * @param node the node
  14. * @param arg the acquire argument
  15. * @return {@code true} if interrupted while waiting
  16. */
  17. final boolean acquireQueued(final Node node, int arg) {
  18. boolean failed = true;
  19. try {
  20. boolean interrupted = false;
  21. for (;;) {
  22. final Node p = node.predecessor();
  23. // p == head 说明当前节点虽然进到了阻塞队列,可是是阻塞队列的第一个,由于它的前驱是head
  24. // 注意,阻塞队列不包含head节点,head通常指的是占有锁的线程,head后面的才称为阻塞队列
  25. // 因此当前节点能够去试抢一下锁
  26. // 这里咱们说一下,为何能够去试试:
  27. // 首先,它是队头,这个是第一个条件,其次,当前的head有多是刚刚初始化的node,
  28. // enq(node) 方法里面有提到,head是延时初始化的,并且new Node()的时候没有设置任何线程
  29. // 也就是说,当前的head不属于任何一个线程,因此做为队头,能够去试一试,
  30. // tryAcquire已经分析过了, 忘记了请往前看一下,就是简单用CAS试操做一下state
  31. if (p == head && tryAcquire(arg)) {
  32. setHead(node);
  33. p.next = null; // help GC
  34. failed = false;
  35. return interrupted;
  36. }
  37. // 到这里,说明上面的if分支没有成功,要么当前node原本就不是队头,
  38. // 要么就是tryAcquire(arg)没有抢赢别人,继续往下看
  39. if (shouldParkAfterFailedAcquire(p, node) &&
  40. parkAndCheckInterrupt())
  41. interrupted = true;
  42. }
  43. } finally {
  44. if (failed)
  45. cancelAcquire(node);
  46. }
  47. }

第一次循环:获取咱们传入node的前驱节点,判断是不是head节点,如今咱们的状态是: 
此处输入图片的描述 
很明显知足当前node节点的前驱节点是head节点,那么如今咱们就要去调用tryAcquire方法,也就是NonfairSync类的tryAcquire方法,而这个方法又调用了ReentrantLock.Sync.nonfairTryAcquire方法。

很明显thread1占用锁,因此thread2获取锁是失败的,直接返回false。按照调用流程,如今进入了当前节点的前驱节点的shouldParkAfterFailedAcquire方法,检查当前节点的前驱节点的waitstatus。shouldParkAfterFailedAcquire方法内部以下:

 
  1. /**
  2. * Checks and updates status for a node that failed to acquire.
  3. * Returns true if thread should block. This is the main signal
  4. * control in all acquire loops. Requires that pred == node.prev.
  5. *
  6. * @param pred node's predecessor holding status
  7. * @param node the node
  8. * @return {@code true} if thread should block
  9. */
  10. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  11. int ws = pred.waitStatus;
  12. // 前驱节点的 waitStatus == -1 ,说明前驱节点状态正常,当前线程须要挂起,直接能够返回true
  13. if (ws == Node.SIGNAL)
  14. /*
  15. * This node has already set status asking a release
  16. * to signal it, so it can safely park.
  17. */
  18. return true;
  19. // 前驱节点 waitStatus大于0 ,以前说过,大于0 说明前驱节点取消了排队。这里须要知道这点:
  20. // 进入阻塞队列排队的线程会被挂起,而唤醒的操做是由前驱节点完成的。
  21. // 因此下面这块代码说的是将当前节点的prev指向waitStatus<=0的节点,
  22. // 简单说,就是为了找个好爹,由于你还得依赖它来唤醒呢,若是前驱节点取消了排队,
  23. // 找前驱节点的前驱节点作爹,往前循环总能找到一个好爹的
  24. if (ws > 0) {
  25. /*
  26. * Predecessor was cancelled. Skip over predecessors and
  27. * indicate retry.
  28. */
  29. do {
  30. node.prev = pred = pred.prev;
  31. } while (pred.waitStatus > 0);
  32. pred.next = node;
  33. } else {
  34. /*
  35. * waitStatus must be 0 or PROPAGATE. Indicate that we
  36. * need a signal, but don't park yet. Caller will need to
  37. * retry to make sure it cannot acquire before parking.
  38. */
  39. // 仔细想一想,若是进入到这个分支意味着什么
  40. // 前驱节点的waitStatus不等于-1和1,那也就是只多是0,-2,-3
  41. // 在咱们前面的源码中,都没有看到有设置waitStatus的,因此每一个新的node入队时,waitStatu都是0
  42. // 用CAS将前驱节点的waitStatus设置为Node.SIGNAL(也就是-1)
  43. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  44. }
  45. return false;
  46. }

上面的流程以下: 
1 若是当前节点的前驱节点的waitStatus=-1,就直接返回true; 
2 若是当前节点的前驱节点的waitStatus>0,也就是前驱节点被取消了,那么就从阻塞队列中删除前驱节点; 
3 若是以上状况都不知足,则经过CAS操做将前驱节点设置为-1(SIGNAL)。 
此时,阻塞队列中,会将head节点的waitStatus由0变为-1(初始化节点的waitStatus都是0),而后返回false; 
此处输入图片的描述 
而后回到acquireQueued执行第二次循环:很明显知足当前node节点的前驱节点是head节点,那么如今咱们就要去调用tryAcquire方法,也就是NonfairSync类的tryAcquire方法,而这个方法又调用了ReentrantLock.Sync.nonfairTryAcquire方法。很明显此时thread2获取锁是失败的,直接返回false。按照调用流程,如今进入了当前节点的前驱节点的shouldParkAfterFailedAcquire方法,检查当前节点的前驱节点的waitstatus。此时waitstatus为-1,这个方法返回true。shouldParkAfterFailedAcquire返回true后,就会调用parkAndCheckInterrupt方法,直接将当前线程thread2中断。

 
  1. // 1. 若是shouldParkAfterFailedAcquire(p, node)返回true,
  2. // 那么须要执行parkAndCheckInterrupt():
  3. // 这个方法很简单,由于前面返回true,因此须要挂起线程,这个方法就是负责挂起线程的
  4. // 这里用了LockSupport.park(this)来挂起线程,而后就停在这里了,等待被唤醒=======
  5. private final boolean parkAndCheckInterrupt() {
  6. LockSupport.park(this);
  7. return Thread.interrupted();
  8. }

仔细看这个方法acquireQueued方法,是无限循环,感受若是p == head && tryAcquire(arg)条件不知足循环将永远没法结束,在这里,固然不会出现死循环。由于parkAndCheckInterrupt会把当前线程挂起,从而阻塞住线程的调用栈。分析到这里,咱们的thread2线程已经被中断了,这个线程不会再继续执行下去了。

3)假设如今咱们的thread1尚未释放锁,而如今又来了一个线程thread3。 
thread3首先调用lock方法获取锁,首先去抢占锁,由于咱们知道thread1尚未释放锁,这个时候thread3确定抢占失败,因而又调用了acquire方法,接着又失败。接着会去调用addWaiter方法,将当前线程thread3封装成node加入到线程阻塞队列的尾部。如今的结构以下: 
此处输入图片的描述 
而后,调用addWaiter方法后,第一步,将当前将要去获取锁的线程也就是thread3和独占模式封装为一个node对象。而且咱们也知道在当前的执行环境下,线程阻塞队列不是空的,由于thread2获取了锁,thread2已经加入了队列。很明显,这个时候队列的尾部tail节点也不是null,那么将直接进入到if分支。将尾部tail节点赋值给咱们传入的node节点的前驱节点。以下: 
此处输入图片的描述
第二步:经过CAS将咱们传递进来的node节点设置成tail节点,而且将新tail节点设置成老tail节点的后继节点。 
此处输入图片的描述
在执行addWaiter方法,将thread3插入到阻塞队列尾部后,而后继续调用acquireQueued方法,这是一个自循环方法。

 
  1. final boolean acquireQueued(final Node node, int arg) {
  2. boolean failed = true;
  3. try {
  4. boolean interrupted = false;
  5. for (;;) {
  6. final Node p = node.predecessor();
  7. if (p == head && tryAcquire(arg)) {
  8. setHead(node);
  9. p.next = null; // help GC
  10. failed = false;
  11. return interrupted;
  12. }
  13. if (shouldParkAfterFailedAcquire(p, node) &&
  14. parkAndCheckInterrupt())
  15. interrupted = true;
  16. }
  17. } finally {
  18. if (failed)
  19. cancelAcquire(node);
  20. }
  21. }

第一次循环:获取thread3节点的前驱节点,判断是不是head节点,如今阻塞队列的结果以下: 
此处输入图片的描述
因为thread3的节点的前驱节点是thread2,不是head,因此会直接调用shouldParkAfterFailedAcquire方法: 
1 判断thread3节点的前驱节点的waitStatus,状态为-1,直接返回true; 
2 若是thread3节点的前驱节点的waitStatus大于0,说明这个节点被CANCEL了,一直循环向前查找,直到找到waitStatus<=0; 
3 若是都不是以上的状况,就经过CAS操做将这个前驱节点设置成-1(SIGHNAL)。 
此时的结构以下,主要是thread2节点的waitStatus由0变成了-1。 
此处输入图片的描述
第二次循环:获取thread3节点的前驱节点,判断是不是head节点,因为明显不是head节点,那么直接进入调用shouldParkAfterFailedAcquire方法,此时,thread3节点的前驱节点的waitStatus为-1,由于返回ture,因此接下来会调用parkAndCheckInterrupt方法,直接将当前线程thread3线程中断。如今thread2和thread3线程都被中断了。

 

3.2 FairSync锁

其实,公平锁和非公平锁,不一样点只存在两个地方: 
1 对于非公平锁,获取锁的第一步就是经过CAS设置state的状态,若是成功,则直接获取了锁; 
2 对于公平是和非公平锁,都会调用tryAcquire方法来获取锁,可是两者是有区别的:

 
  1. //非公平锁
  2. /**
  3. * Performs non-fair tryLock. tryAcquire is implemented in
  4. * subclasses, but both need nonfair try for trylock method.
  5. */
  6. final boolean nonfairTryAcquire(int acquires) {
  7. final Thread current = Thread.currentThread();
  8. int c = getState();
  9. if (c == 0) {
  10. // 非公平锁,直接抢占锁
  11. if (compareAndSetState(0, acquires)) {
  12. setExclusiveOwnerThread(current);
  13. return true;
  14. }
  15. }
  16. else if (current == getExclusiveOwnerThread()) {
  17. int nextc = c + acquires;
  18. if (nextc < 0) // overflow
  19. throw new Error("Maximum lock count exceeded");
  20. setState(nextc);
  21. return true;
  22. }
  23. return false;
  24. }
  25. //公平锁
  26. /**
  27. * Fair version of tryAcquire. Don't grant access unless
  28. * recursive call or no waiters or is first.
  29. */
  30. protected final boolean tryAcquire(int acquires) {
  31. final Thread current = Thread.currentThread();
  32. int c = getState();
  33. if (c == 0) {
  34. // 虽然此时此刻锁是能够用的,可是这是公平锁,既然是公平,就得讲究先来后到,
  35. // 看看有没有别人在队列中等了半天了
  36. if (!hasQueuedPredecessors() &&
  37. compareAndSetState(0, acquires)) {
  38. setExclusiveOwnerThread(current);
  39. return true;
  40. }
  41. }
  42. else if (current == getExclusiveOwnerThread()) {
  43. int nextc = c + acquires;
  44. if (nextc < 0)
  45. throw new Error("Maximum lock count exceeded");
  46. setState(nextc);
  47. return true;
  48. }
  49. return false;
  50. }
  51. }
 

4 ReentrantLock 释放锁

如今thread1要开始释放锁了。调用unlock方法,unlock方法又调用了内部的release方法:

 
  1. /**
  2. * Releases in exclusive mode. Implemented by unblocking one or
  3. * more threads if {@link #tryRelease} returns true.
  4. * This method can be used to implement method {@link Lock#unlock}.
  5. *
  6. * @param arg the release argument. This value is conveyed to
  7. * {@link #tryRelease} but is otherwise uninterpreted and
  8. * can represent anything you like.
  9. * @return the value returned from {@link #tryRelease}
  10. */
  11. public final boolean release(int arg) {
  12. if (tryRelease(arg)) {
  13. Node h = head;
  14. if (h != null && h.waitStatus != 0)
  15. unparkSuccessor(h);
  16. return true;
  17. }
  18. return false;
  19. }
  20. protected final boolean tryRelease(int releases) {
  21. int c = getState() - releases;
  22. if (Thread.currentThread() != getExclusiveOwnerThread())
  23. throw new IllegalMonitorStateException();
  24. // 是否彻底释放锁
  25. boolean free = false;
  26. // 其实就是重入的问题,若是c==0,也就是说没有嵌套锁了,能够释放了,不然还不能释放掉
  27. if (c == 0) {
  28. free = true;
  29. setExclusiveOwnerThread(null);
  30. }
  31. setState(c);
  32. return free;
  33. }

调用tryRelease方法释放锁,获取当前AQS的state,并减去1,判断当前线程是否等于AQS的exclusiveOwnerThread,若是不是,就抛异常,这就保证了加锁和释放锁必须是同一个线程。若是(state-1)的结果不为0,说明锁被重入了,须要屡次unlock。若是(state-1)等于0,咱们就将AQS的ExclusiveOwnerThread设置为null。若是上述操做成功了,也就是tryRelase方法返回了true,那么就会判断当前队列中的head节点,当前结构以下: 
此处输入图片的描述
若是head节点不为null,而且head节点的waitStatus不为0,咱们就调用unparkSuccessor方法去唤醒head节点的后继节点。

 
  1. /**
  2. * Wakes up node's successor, if one exists.
  3. *
  4. * @param node the node
  5. */
  6. // 唤醒后继节点
  7. // 从上面调用处知道,参数node是head头结点
  8. private void unparkSuccessor(Node node) {
  9. /*
  10. * If status is negative (i.e., possibly needing signal) try
  11. * to clear in anticipation of signalling. It is OK if this
  12. * fails or if status is changed by waiting thread.
  13. */
  14. int ws = node.waitStatus;
  15. // 若是head节点当前waitStatus<0, 将其修改成0
  16. if (ws < 0)
  17. compareAndSetWaitStatus(node, ws, 0);
  18. /*
  19. * Thread to unpark is held in successor, which is normally
  20. * just the next node. But if cancelled or apparently null,
  21. * traverse backwards from tail to find the actual
  22. * non-cancelled successor.
  23. */
  24. // 下面的代码就是唤醒后继节点,可是有可能后继节点取消了等待(waitStatus==1)
  25. // 从队尾往前找,找到waitStatus<=0的全部节点中排在最前面的
  26. Node s = node.next;
  27. if (s == null || s.waitStatus > 0) {
  28. s = null;
  29. // 从后往前找,仔细看代码,没必要担忧中间有节点取消(waitStatus==1)的状况
  30. for (Node t = tail; t != null && t != node; t = t.prev)
  31. if (t.waitStatus <= 0)
  32. s = t;
  33. }
  34. if (s != null)
  35. // 唤醒线程
  36. LockSupport.unpark(s.thread);
  37. }

第一步:获取head节点的waitStatus,若是小于0,就经过CAS操做将head节点的waitStatus修改成0,如今是: 
此处输入图片的描述
第二步:寻找head节点的下一个节点,若是这个节点的waitStatus小于0,就唤醒这个节点,不然遍历下去,找到第一个waitStatus<=0的节点,并唤醒。如今thread2线程被唤醒了,咱们知道刚才thread2在acquireQueued被中断,如今继续执行,又进入了for循环,当前节点的前驱节点是head而且调用tryAquire方法得到锁而且成功。那么设置当前Node为head节点,将里面的thead和prev设置为null。 
此处输入图片的描述此时,thread2获取了锁,并成为头节点,原来的头节点释放掉,等待被回收。

相关文章
相关标签/搜索