一、Lock

lock接口是jdk源码里重要的组件

1. lock实现

lock是一个接口,它定义了获取锁跟释放锁的方法,lock定义成接口说明它是锁的一个规范,具体该怎么实现,由其实现类进行特性。其中ReentrantLock是唯一直接实现了Lock的接口

  1. ReentrantLock:表示可重入锁 ,可重入锁的含义就是,当某一个线程调用方法获取锁之后,倘若再次调用方法获取锁后,该线程不会被阻塞,而是直接累加计数器
  2. ReentrantReadWriteLock:可重入读写锁,它实现了ReadWriteLock接口
  3. StampedLock:

    2.lock结构

    1. void lock();//获取锁,获取不到锁则当前线程被阻塞,直到锁被释放
    2. void lockInterruptibly() throws InterruptedException;//跟lock方法相似,不过该方法可以 让被阻塞的线程中断唤醒,抛出异常
    3. boolean tryLock();//尝试获取锁,获取锁成功返回true,失败返回false
    4. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//带超时时间的尝试获取锁,超时抛出异常
    5. void unlock();//释放锁

    二、ReentrantLock

    reentrantLock 代表可重入锁,可重入锁的含义指的是当线程A占用锁之后,再次去获取锁的时候,线程A不会被阻塞,而是直接在计数器累加,返回。

    1、ReentrantLock使用

    三、 ReentrantLock的实现原理?

    锁通常是将多线程并行的任务采用某一种机制,让线程变得串行,这样就保证线程的安全,那ReentrantLock是如何实现线程的安全?
    需要我们分析ReentrantLock的结构。

    1、AQS

    在ReentrantLock类中,有一个类AbstractQueuedSynchronizer,简称AQS,俗称抽象队列同步器,该类是保证ReentrantLock实现线程安全的重要一个类;ReentrantLock的获取锁,释放锁都需要该类进行支持

    2、AQS的功能

    AQS从使用角度上来说,分为两种功能:

  4. 独占模式:指的是每次只能有一个线程占用锁

  5. 共享模式:允许多线程共同拥有一把锁,共享资源

    3、AQS的内部结构

    AQS内部维护了一个FIFO先进先出的队列(双向链表),Head永远都指向当前占用锁的线程,Tail指向队尾节点,head.next节点任何时候都有权力去尝试获取锁。每一个Node节点都有前驱节点跟后继节点,这样的结构能够很方便的知道一个节点的前驱跟后继节点,每一个Node节点都其实指的是一个线程,一个由线程包装的Node节点,当线程抢占锁失败后,线程会被封装为Node节点,然后被添加到AQS阻塞队列里,当占用锁的线程释放锁之后,会从AQS阻塞队列里唤醒一个Node节点(唤醒Node对应的线程)。
    image.png

    1. //头节点,任何时候头节点对应的线程都是当前占用锁的线程
    2. private transient volatile Node head;
    3. //阻塞队列的尾节点(阻塞队列不包含head节点 head.next -->tail 阻塞队列)
    4. private transient volatile Node tail;
    5. //表示资源
    6. //独占模式下 0 ->未加锁状态 1->已加锁状态
    7. private volatile int state;
    8. //aqs的父类的属性,独占模式下,表示当前持有锁的线程
    9. private transient Thread exclusiveOwnerThread;

    1、Node结构

    1. static final class Node {
    2. //枚举:共享模式
    3. static final Node SHARED = new Node();
    4. //枚举:独占模式
    5. static final Node EXCLUSIVE = null;
    6. //表示当前节点处于 取消 状态
    7. static final int CANCELLED = 1;
    8. //表示当前节点需要唤醒它的后继节点(SIGNAL其实是后继节点的状态,需要当前节点去喊它)
    9. static final int SIGNAL = -1;
    10. static final int CONDITION = -2;
    11. static final int PROPAGATE = -3;
    12. //node状态,可选值(0,SIGNAL(-1),CANCELLED(1),CONDITION(-2),PROPAGATE(-3))
    13. //默认waitStatus == 0,waitStatus> 0 取消状态
    14. //waitStatus == -1 表示当前节点如果是head节点,释放锁之后,需要唤醒他的后继节点
    15. volatile int waitStatus;
    16. //因为需要构造fifo队列,所有prev 指向前继节点
    17. volatile Node prev;
    18. //因为需要构造fifo队列,所有next 指向后继节点
    19. volatile Node next;
    20. //当前node封装的线程本身
    21. volatile Thread thread;
    22. //
    23. Node nextWaiter;
    24. Node() {}
    25. Node(Thread thread, Node mode) {
    26. this.nextWaiter = mode;
    27. this.thread = thread;
    28. }
    29. Node(Thread thread, int waitStatus) {
    30. this.waitStatus = waitStatus;
    31. this.thread = thread;
    32. }

    2、抢占锁失败添加Node入队列

    线程抢占锁失败后会将该线程封装为Node节点添加到阻塞队列里,队列会发生变化,其中入队过程分为三步:

  6. 新添加的节点要主动做事情,将node.prev前驱节点设置为当前tail节点

  7. 使用cas方式将当前tail节点修改为新添加的Node节点,为什么使用cas方式,因为这里可能多线程写入队列

  8. cas成功后,将老的tail节点的next指向当前tail节点,完成双向绑定

image.png

3、释放锁唤醒Node

当占用所的线程释放锁之后,需要从AQS阻塞队列里唤醒一个Node,此时阻塞队列的节点也会发生变化:
唤醒Node分为两步

  1. 将head指向唤醒的Node节点,Node节点的Thread属性置为null,Node的prev置为null
  2. 将老的head节点的next置为null
    image.png

    4、ReentrantLock的源码分析

    在进行分析前,我们还是看下ReentrantLock的结构
    image.png
    ReentrantLock内部有一个Sync类,Sync是一个抽象静态内部类,继承了AQS来实现重入锁的逻辑,子类有FailSyncNoFailSync两个,分别代表公平锁,非公平锁,图形象的说明了ReenTrantLock的实现是靠AQS来实现。
    公平锁跟非公平锁的唯一区别在于:非公平锁在获取锁的时候不会考虑阻塞队列是否有等待者,而是一上来就使用cas方式去获取锁,获取锁成功则返回true,失败则尝试去获取锁,在尝试获取锁,无锁前提下,也不会考虑是否有等待者,也是直接cas方式去获取锁,成功返回true

    1、加锁逻辑

    ReentrantLock.lock

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

    直接调用sync的lock方法,当我们在实例化ReentrantLock时候默认创建的是NoFailLock类,只有实例化ReentrantLock,传递参数true,才会实例化公平锁,这里主要看公平锁的逻辑,更加全面。

FailSync.lock

  1. final void lock() {
  2. acquire(1);
  3. }

这里是直接调用AQS的acquire方法

AQS.acquire

  1. public final void acquire(int arg) {
  2. //条件一:tryAcquire(arg)尝试获取锁,获取成功返回true 失败返回false
  3. //条件二:2.1:addWaiter(Node.EXCLUSIVE), arg) 将当前线程封装node入队列
  4. // 2.2:acquireQueued 挂起当前线程,唤醒后相关逻辑
  5. // acquireQueued 返回true 表示挂起线程过程中线程被中断唤醒过,false未被中断过
  6. if (!tryAcquire(arg) &&
  7. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  8. //再次设置中断标记为true
  9. selfInterrupt();
  10. }

acquire方法主要做了三步:

  1. 调用tryAcquire尝试去获取锁,获取锁成功返回true,失败返回false

  2. 如果获取锁失败,则通过addWaiter 将当前线程封装为Node节点添加到AQS阻塞队列队尾,并返回当前添加的Node节点

  3. acquireQueued ,将Node作为参数,去执行 将线程park阻塞;线程唤醒之后的逻辑

    1、FailSync.tryAcquire
  4. 获取当前线程,判断当前锁的状态

  5. 如果state==0,表示无锁,公平锁讲究先来后到,判断队列是否有等待线程,没有则通过cas方式去更新state的值
  6. 当前线程属于重入,直接更新增加state的值

    1. //抢占锁成功返回true,失败返回false
    2. protected final boolean tryAcquire(int acquires) {
    3. //获取当前线程
    4. final Thread current = Thread.currentThread();
    5. //AQS state值
    6. int c = getState();
    7. //条件成立,表示当前AQS处于无锁状态
    8. if (c == 0) {
    9. //条件一:因为FailSync是公平锁,任何时候需要检查一下,队列中是否在当前线程之前有等待者...
    10. //hasQueuedPredecessors方法返回true 当前线程前面有等待者,需要入队列
    11. //hasQueuedPredecessors方法返回false 当前线程前面没有等待者 ,直接尝试获取锁
    12. //条件二:compareAndSetState(0, acquires)
    13. //true 说明当前线程抢占锁成功
    14. //false 说明存在竞争,抢占锁失败
    15. if (!hasQueuedPredecessors() &&
    16. compareAndSetState(0, acquires)) {
    17. //成功之后设置当前线程为独占锁 线程
    18. setExclusiveOwnerThread(current);
    19. return true;
    20. }
    21. }
    22. //1. c=state!=0,这种情况需要检查一下当前线程是不是 独占锁的线程,因为ReentranLock是可以重入的
    23. //条件成立,说明当前线程就是独占锁的线程
    24. else if (current == getExclusiveOwnerThread()) {
    25. //重入逻辑
    26. //更新state值
    27. int nextc = c + acquires;
    28. //越界判断
    29. if (nextc < 0)
    30. throw new Error("Maximum lock count exceeded");
    31. //更新值
    32. setState(nextc);
    33. return true;
    34. }
    35. //执行到这里?
    36. //1. cas失败,c==0,cas state时候发生竞争了,抢占锁失败
    37. //2. c>0,且ExclusiveOwnerThread != 当前线程
    38. return false;
    39. }

    2、AQS.addWaiter

    当tryAcquire获取锁失败后,会调用addWaiter方法将抢占锁失败的线程,封装为Node节点,addWaiter传参mode是Node.EXCLUSIVE,表示独占状态,意味着重入锁使用了AQS的独占模式来保证线程安全。addWaiter方法分为三步:

  7. 当前线程封装为Node节点

  8. 判断队列是否为空,不为空则cas tail节点将创建的Node节点添加到队列

  9. 如果队列为空或者cas tail节点失败,调用enq 完整入队列

    1. //Node节点入队列,最终返回当前线程封装的Node节点
    2. private Node addWaiter(Node mode) {
    3. //构建node,把当前线程封装到node中
    4. Node node = new Node(Thread.currentThread(), mode);
    5. //获取队尾节点
    6. Node pred = tail;
    7. //快速入队
    8. //条件成立,说明当前队列不为空,有node节点了
    9. if (pred != null) {
    10. node.prev = pred;
    11. //cas成功,说明入队成功,false表示发生竞争
    12. if (compareAndSetTail(pred, node)) {
    13. //前置节点指向创建节点,完成双向绑定
    14. pred.next = node;
    15. return node;
    16. }
    17. }
    18. //什么情况执行到这里?
    19. //1.当前队列是空队列
    20. //2. cas tail失败
    21. //完整入队
    22. enq(node);
    23. return node;
    24. }

    1、AQS.enq

    enq就是通过自旋操作将Node节点入队列
    这里分为两步:

  10. 如果队列为空,向队列补充一个Node节点(这种情况发生在第一个获取锁失败的线程,因为占用锁的线程只是将exclusiveOwnerThread设置了下,并没有向阻塞队列里添加节点,因此第一个获取锁失败的线程需要先给占用锁的线程搽屁股)

  11. 队列不为空则cas tail方式入队列

![{[}QZI678TATN@C$}I}`6V.png

  1. //AQS的enq方法
  2. private Node enq(final Node node) {
  3. //自旋入队,只有入队成功才会跳出循环
  4. for (;;) {
  5. Node t = tail;
  6. //1.当前队列是空队列,tail ==null
  7. //说明当前锁被占用,且当前线程 有可能是第一个获取锁失败的线程(当前时刻有可能有多个获取锁失败的线程)
  8. if (t == null) { // Must initialize
  9. //作为当前持锁线程 第一个 后继线程,需要做什么事?
  10. //1.需要创建一个node 将head节点指向该node;因为持有锁的线程当时只是将线程本身赋值给了ExclusiveOwnerThread,并没有向阻塞队列里添加任何node,所以后继节点要为它搽屁股
  11. //2.将自己添加到队列
  12. if (compareAndSetHead(new Node()))
  13. tail = head;
  14. //这里并没有直接return
  15. } else {
  16. //普通入队方式,只不过在for循环中,保证一定入队成功
  17. node.prev = t;
  18. if (compareAndSetTail(t, node)) {
  19. t.next = node;
  20. return t;
  21. }
  22. }
  23. }
  24. }

3、AQS.acquireQueued

通过addWaiter方法将Node添加到队列后,然后把Node作为参数 调用acquireQueued,去阻塞或者竞争锁

  1. 获取当前node的prev前驱节点

  2. 如果前驱节点是head节点,head.next节点有权力去尝试获取锁,调用tryAcquire 去尝试获取锁

  3. 获取锁成功,将head设置为当前node,将老的head节点出队列

  4. 获取锁失败,根据node的waitStatus状态决定是否park Node对应的线程

  5. 非公平锁中最后 如果被中断唤醒会通过cancelAcquire 将node出队列

    1. //需要做什么?
    2. //1.入队的线程park
    3. //2.唤醒线程之后的逻辑
    4. //AQS的acquireQueued方法
    5. //参数1:node 当前线程包装出来的node,且node节点已经入队成功
    6. //2. 当前线程抢占资源成功后,设置state时,使用到的
    7. final boolean acquireQueued(final Node node, int arg) {
    8. //true表示当前线程抢占锁成功,false表示抢占失败,需要执行出队的逻辑
    9. boolean failed = true;
    10. try {
    11. //当前线程是否被中断
    12. boolean interrupted = false;
    13. //自旋
    14. for (;;) {
    15. //什么时候执行这里?
    16. //1 在线程未park前
    17. //2.线程被unaprk唤醒后
    18. //获取当前节点的前置节点
    19. final Node p = node.predecessor();
    20. //条件1成立;说明当前节点是head.next节点,head.next任何时候有权力尝试再次获取锁
    21. //条件2成立:说明head已经释放锁了并且当前node对应的线程(head.next)也获取到锁了
    22. //不成立: 说明head对应的线程 还未释放锁,head.next仍需要被park
    23. if (p == head && tryAcquire(arg)) {
    24. //设置head为当前node 对应的线程
    25. setHead(node);
    26. //清除老head节点,协助老的head节点出队
    27. p.next = null; // help GC
    28. //当前线程获取锁过程中没有异常
    29. failed = false;
    30. //返回当前线程的中断标记
    31. return interrupted;
    32. }
    33. //shouldParkAfterFailedAcquire() 返回:true 当前线程需要被挂起;false->不需要
    34. //parkAndCheckInterrupt()作用?挂起当前线程,并且唤醒之后 返回当前线程的中断标记
    35. //唤醒(1.被其他线程unpark方式正常唤醒 2.其他线程给当前挂起线程一个中断信号)
    36. if (shouldParkAfterFailedAcquire (p, node) &&
    37. parkAndCheckInterrupt())
    38. //条件成立:说明当前node对应的线程是被中断唤醒的
    39. interrupted = true;
    40. }
    41. } finally {
    42. if (failed)
    43. cancelAcquire(node);
    44. }
    45. }

    1、AQS.setHead
    1. private void setHead(Node node) {
    2. head = node;
    3. node.thread = null;
    4. node.prev = null;
    5. }

    2、AQS.shouldParkAfterFailedAcquire

    倘若占用锁的线程还未释放锁,那么其他线程获取锁就会失败,失败就会被封装为Node节点添加到队列,然后调用shouldParkAfterFailedAcquire方法。
    该方法主要是 根据获取锁失败Node的前驱节点的 waitStatus状态,判断当前线程在获取锁失败之后 是否要被阻塞挂起,返回true,该线程会被挂起,反之不会被挂起。

Node的waitStatus是什么? Node类有一个waitStatus属性,该属性有5个值,分别为-3(PROPAGATE状态) 、-2(CONDITION状态)-1(SIGNAL状态) 0(默认状态)、1(CANCEL状态)

  • CANCEL:线程在队列里被中断唤醒之后,需要将node对应的线程从队列里移除出去,会将node节点的waitStatus状态修改为CANCEL,即取消状态
  • 0:node节点默认状态
  • SIGNAL:释放锁之后,会通知状态<=0 SIGNAL状态的节点唤醒
  • CONDITION:跟condition有关
  • PROPAGATE:共享模式下,PROPAGATE状态的线程处于可运行状态
  1. waitStatus ==SIGNAL 状态,返回true

  2. waitStatus >0 (CANCEL状态),通过循环将 CANCEL状态的节点移除出去

  3. waitStatus ==0 cas方式将node节点值修改为 SIGNAL状态

    1. //AQS 的shouldParkAfterFailedAcquire方法
    2. //总结
    3. //1.当前节点的前置节点的状态是 取消cancel状态, 第一次来到该方法,会越过取消状态的节点返回false,第二次来 会返回true然后park当前线程
    4. //2.当前节点的前置节点状态是 0状态,强制设置前置节点是 singal(-1),第二次自旋来到这个方法时返回true,然后park当前线程
    5. //参数一:pred 当前线程的node 的前置节点
    6. //参数二:node 当前线程对应的node节点
    7. //返回值 true表示当前线程需要挂起;false->不需要
    8. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    9. //获取前置节点的状态
    10. //0 默认状态new node();-1 singal 表示当前节点释放锁之后会唤醒他的第一个后继节点;1 cancel状态
    11. int ws = pred.waitStatus;
    12. //条件成立:表示前置节点是一个可以唤醒当前节点的节点,所有返回true->parkAndCheckInterrupt()方法park当前线程
    13. //普通情况,第一次来到该方法,ws不会是-1
    14. if (ws == Node.SIGNAL)
    15. return true;
    16. //条件成立,ws前置节点是cancel状态
    17. if (ws > 0) {
    18. //从node节点开始向前面找ws<=0的节点,
    19. do {
    20. node.prev = pred = pred.prev;
    21. } while (pred.waitStatus > 0);
    22. //找到之后,退出循环,过程中会将ws>0(cancel状态的节点出队)
    23. pred.next = node;
    24. } else {
    25. //当前node前置节点的ws 状态就是 0情况下
    26. //将当前线程node的前置节点 状态强制设置为-1 singal,表示前置节点释放锁之后 需要唤醒我...
    27. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    28. }
    29. return false;
    30. }

    返回true,获取锁失败的线程会调用parkAndCheckInterrupt ,让线程挂起;返回false,不需要挂起。

    3、AQS.parkAndCheckInterrupt

    shouldParkAfterFailedAcquire返回true之后,调用parkAndCheckInterrupt 让线程挂起

    1. private final boolean parkAndCheckInterrupt() {
    2. LockSupport.park(this);
    3. return Thread.interrupted();//返回线程是否被中断挂起的布尔值
    4. }

    4、AQS.cancelAcquire

    当线程被中断唤醒之后,parkAndCheckInterrupt方法返回true,不带中断的获取锁方法不会做任何操作,带中断的获取锁方法会直接抛出中断异常,然后通过将cancelAcquire将被中断唤醒的node节点出队列。出队列根据当前node的位置分为三种情况:

  4. node节点是tail队尾节点,cas方式直接出队列

  5. node节点是 head.next节点
  6. node节点既不是head.next节点也不是tail节点 ```java

private void cancelAcquire(Node node) { if (node == null) return; //因为已经取消排队了,直接将node关联的线程thread置为null node.thread = null; //获取当前取消排队node的前继节点 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; //拿到前驱的后继节点 //1.当前node节点 //2.waitStatus>0的节点 Node predNext = pred.next; //将当前node的状态设置为 取消状态 1 node.waitStatus = Node.CANCELLED;

  1. //当前node在队列的位置不同,执行的出队策略也不同,分三种情况:
  2. //1.当前node节点是tail 队尾节点
  3. //2.当前node节点不是head.next节点也不是tail节点
  4. //3.当前node节点是head.next节点
  5. //条件一 node == tail 成立:当前node节点是tail节点
  6. //条件二 compareAndSetTail(node, pred) 成立,说明修改tail成功
  7. //情况1 当前node节点是tail节点
  8. if (node == tail && compareAndSetTail(node, pred)) {
  9. //修改pred.next置为null,完成node出队
  10. compareAndSetNext(pred, predNext, null);
  11. } else {
  12. //保存node的状态
  13. int ws;
  14. //第二种情况,当前node 不是head.next也不是tail节点
  15. //条件一:pred != head 成立 当前node 不是head.next也不是tail节点
  16. //条件二:((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) 是为了保证前驱节点的状态是singal状态,因为if里面做的事情就是pred.next -> node.next
  17. if (pred != head &&
  18. ((ws = pred.waitStatus) == Node.SIGNAL ||
  19. (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
  20. pred.thread != null) {
  21. //情况2 当前node 不是head.next也不是tail节点
  22. //出队 pred.next -> node.next节点后,当node.next节点被唤醒后,调用shouldParkAfterFailedAcquire
  23. //会让node.next节点越过取消状态的节点,完成真正出队
  24. Node next = node.next;
  25. if (next != null && next.waitStatus <= 0)
  26. compareAndSetNext(pred, predNext, next);
  27. } else {
  28. //情况3 当前node节点是head.next节点
  29. //类似情况2 node的后继节点唤醒后,调用shouldParkAfterFailedAcquire会让node.next跳过取消状态的node节点,也就会跳过要出队的node节点,让node.next与head进行绑定
  30. unparkSuccessor(node);
  31. }
  32. node.next = node; // help GC
  33. }

}

  1. <a name="TDSTu"></a>
  2. ### 2、释放锁逻辑
  3. 调用ReentrantLock.unlock方法,释放锁,会发生什么?
  4. <a name="JslSG"></a>
  5. #### 1、reentrantLock.unlock
  6. ```java
  7. public void unlock() {
  8. sync.release(1);
  9. }

还是看公平锁

2、AQS.release

release方法尝试去释放锁,如果锁完全释放成功,通过unparkSuccessor方法唤醒队列里的一个Node节点

  1. public final boolean release(int arg) {
  2. //tryRelease 返回true 当前线程完全释放锁;false当前线程尚未完全释放锁
  3. if (tryRelease(arg)) {
  4. //head节点是在有一个线程占用锁期间,有其他线程发现获取不了且队列是空时候,后续线程会创建一个节点赋值给head
  5. //拿到head节点
  6. Node h = head;
  7. //条件1成立 说明队列的head已经初始化了,ReentranLock在使用期间发生了 多线程竞争
  8. //条件二成立 说明当前head后面有节点
  9. if (h != null && h.waitStatus != 0)
  10. //去唤醒后继节点
  11. unparkSuccessor(h);
  12. return true;
  13. }
  14. return false;
  15. }

1、Sync.tryRelease

方法去尝试释放锁,因为reentrantLock是允许重入锁的,因此返回true表示占用锁线程完全释放锁,false表示未完全释放锁

  1. //true 表示当前线程完全释放锁;false未完全释放锁
  2. protected final boolean tryRelease(int releases) {
  3. //减去释放的值
  4. int c = getState() - releases;
  5. //在释放锁之前 先判断下当前线程是否是占用锁的线程,不是抛出异常
  6. if (Thread.currentThread() != getExclusiveOwnerThread())
  7. throw new IllegalMonitorStateException();
  8. //是否已经完全释放锁
  9. boolean free = false;
  10. //条件成立,说明当前线程已经达到完全释放锁的条件
  11. if (c == 0) {
  12. free = true;
  13. //设置ExclusiveOwnerThread为null
  14. setExclusiveOwnerThread(null);
  15. }
  16. //更新AQS.state
  17. setState(c);
  18. return free;
  19. }

2、AQS.unparkSuccessor

unparkSuccessor方法做了两步操作

  1. 将当前head节点的waitStatus cas方式 从signal转为 0
  2. 获取head.next节点,从tail节点开始从后向前,查找最靠近head的 状态小于等于0的Node节点,如果存在,则唤醒该节点

    1. //AQS的unparkSuccessor方法
    2. //唤醒当前节点的下一个节点
    3. private void unparkSuccessor(Node node) {
    4. int ws = node.waitStatus;
    5. //singal状态
    6. if (ws < 0)
    7. //当前节点已经完成唤醒后继结点的任务了
    8. compareAndSetWaitStatus(node, ws, 0);
    9. Node s = node.next;
    10. //s什么时候为null
    11. //1.node==tail节点
    12. //2 新节点入队未完成时(1.node.prev=pred 2.cas tail 3. pred.next=node 第三步未完成)
    13. //条件2:前置条件s!=null, 成立说明,当前node的后置节点是cancel状态
    14. if (s == null || s.waitStatus > 0) {
    15. s = null;
    16. for (Node t = tail; t != null && t != node; t = t.prev)
    17. if (t.waitStatus <= 0)
    18. s = t;
    19. //上面循环,从队尾开始向前查找,会找到一个离当前node最近的一个可以被唤醒的node(节点状态<=0),node 可能找不到 有可能是null
    20. }
    21. //如果找到合适的可以被唤醒的node,unpark唤醒
    22. if (s != null)
    23. LockSupport.unpark(s.thread);
    24. }

    当唤醒节点之后,因为我们是在acquireQueued方法里面自旋被挂起的,因此唤醒之后回到自旋,判断当前线程是不是head.next节点,如果是,尝试获取锁,获取锁成功则将head指向现在占用锁的线程,将老的head节点出队列


以上是对公平锁的分析,那么非公平锁又有什么不同呢?

  1. 非公平锁在获取锁的时候,会先通过cas 去获取锁,公平锁不会
  2. 在cas失败后,调用同样的acquire方法,去获取锁,这里不同的是,当无锁情况下,非公平锁不会判断队列是否有等待者,而是直接cas去获取锁;公平锁会判断队列是否有等待者,没有的话才cas去获取锁。

总结:

ReenTranLock lock 方法先尝试获取锁,获取不到则将线程封装为node节点,保存到阻塞队列,然后在自旋中 完成park阻塞当前线程和被唤醒后,看是否是head.next节点,是的话尝试获取锁,获取成功则替换老的head节点。

带有中断机制的lockInterruptibly方法,在阻塞线程中如果线程被中断信号唤醒,会抛异常,调用cancelAcquire方法让node出队列,修改node的状态为cancel状态

出队列分三种情况:

  1. node是tail队尾节点 直接让node出队列
  2. 当前node节点是head.next节点 唤醒node的后继节点,在后继节点唤醒之后执行出node操作
  3. 当前node节点不是head.next节点也不是tail节点 让node的前继节点指向node的后继节点

    五、condition条件队列

    条件队列Condition原理图
    image.png

    1. AQS.ConditionObject

    1. public class ConditionObject implements Condition, java.io.Serializable {
    2. private static final long serialVersionUID = 1173984872572414699L;
    3. //指向条件队列的第一个node节点
    4. private transient Node firstWaiter;
    5. //指向条件队列的最后一个node节点
    6. private transient Node lastWaiter;
    7. }

    2. Condition接口

    1. public interface Condition {
    2. void await() throws InterruptedException;
    3. void awaitUninterruptibly();
    4. long awaitNanos(long nanosTimeout) throws InterruptedException;
    5. boolean await(long time, TimeUnit unit) throws InterruptedException;
    6. boolean awaitUntil(Date deadline) throws InterruptedException;
    7. void signal();
    8. void signalAll();
    9. }

    3. lock.newCondition

    创建一个等待队列

    1. public Condition newCondition() {
    2. return sync.newCondition();
    3. }
    1. final ConditionObject newCondition() {
    2. return new ConditionObject();
    3. }

    最终返回一个AQS的ConditionObject类

    4.Condition.await

    这个方法的逻辑就是,先判断当前线程是否被中断,是则直接抛中断异常,否则

  4. 第一步:将当前线程封装为node节点,添加到条件队列队尾

  5. 第二步:释放当前线程所占用的所有锁,(只有释放了自己占用的锁,才能让其他线程唤醒自己)
  6. 第三步:循环判断是否有占用锁的线程调用了 当前线程的signal方法

    1. 如果调用了signal方法,表示当前线程已经迁移到了阻塞队列,接下来参与竞争锁的逻辑
    2. 如果没有调用signal方法,表示当前线程还在条件队列里,此时走 挂起该线程的逻辑;其中如果在条件队列里挂起过程中,被其他线程中断信号唤醒,也会退出循环,参与竞争锁的逻辑

      1. public final void await() throws InterruptedException {
      2. //判断当前线程是否已经是中断状态,如果是直接抛出中断异常
      3. if (Thread.interrupted())
      4. throw new InterruptedException();
      5. //将调用await方法的线程包装成node并加入到条件队列,并返回当前线程的node
      6. Node node = addConditionWaiter();
      7. //完全释放掉当前线程对应的锁(将state置为0)
      8. //为什么要释放锁呢? 自己加着锁,然后挂起后,谁还能来唤醒你呢
      9. int savedState = fullyRelease(node);
      10. //0 在condition队列挂起期间未接收过中断信号
      11. //-1 在condition队列挂起期间接收到中断信号了
      12. //1 在condition队列挂起期间未接收到中断信号,但是在迁移到 AQS的阻塞队列 之后,接收过中断信号
      13. int interruptMode = 0;
      14. //isOnSyncQueue方法返回true 表示当前线程已经迁移到 AQS的阻塞队列了
      15. //返回false 说明当前node还在条件队列中,需要继续park
      16. while (!isOnSyncQueue(node)) {
      17. //挂起当前node对应的线程
      18. LockSupport.park(this);
      19. //什么时候会被唤醒?
      20. //1.正常情况,当前线程被其他线程调用signal方法转移到阻塞队列,然后获取到锁之后,会唤醒
      21. //2.迁移到阻塞队列中,发现阻塞队列中的前驱节点状态 是取消状态或者修改前驱节点状态-1失败,会唤醒当前节点
      22. //3.当前节点在条件队列被park挂起期间,被外部线程中断信号唤醒
      23. //checkInterruptWhileWaiting:就算再condition条件队列挂起期间 线程发生中断了,对应的node也会被迁移到 AQS的阻塞队列
      24. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
      25. break;
      26. }
      27. //执行到这里,说明当前node被其他线程调用signal方法,已经迁移到 AQS的阻塞队列了
      28. //acquireQueued(node, savedState) 条件成立,表示在阻塞队列中被中断唤醒过
      29. //interruptMode != THROW_IE 成立 说明当前node在条件队列内未发生过中断唤醒
      30. //条件成立:说明当前线程在阻塞队列竞争锁的时候发生中断, && 中断标识位不是 THROW_IE
      31. if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
      32. //将当前线程的中断标识位设置为重新中断
      33. interruptMode = REINTERRUPT;
      34. //条件成立,代表当前node在条件队列中被中断唤醒了,会加入到阻塞队列,但是并未设置node.nextWaiter==null
      35. if (node.nextWaiter != null)
      36. //清理条件队列内取消状态节点,让取消状态节点出队列
      37. unlinkCancelledWaiters();
      38. //条件成立,说明挂起期间被中断过 1条件队列被中断,2条件队列之外被中断
      39. if (interruptMode != 0)
      40. reportInterruptAfterWait(interruptMode);
      41. }

      5. condition.addConditionWaiter

      线程入条件队列逻辑

  7. 判断队列是否有元素,有但是元素状态不对,则进行一次全队的清理工作,清除取消状态的节点,重新获取队尾节点,因为有可能清除时候改变了队尾节点

  8. 线程封装为node节点,判断队列是否有元素?
  9. 如果没有,将头节点指向node
  10. 如果有,将队尾节点的next指向node
  11. 更新队尾节点为node,并返回当前线程的node
    1. //将当前线程封装为node添加到condition条件队列去
    2. //调用await的线程 都是已经持有锁的状态了,因此这里不会发生并发
    3. private Node addConditionWaiter() {
    4. //获取当前队列的尾节点 保存到局部变量t中
    5. Node t = lastWaiter;
    6. //条件一:t != null条件成立:说明队列已经有元素了
    7. //node在条件队列里面 状态是-2 CONDITION
    8. //条件二:t.waitStatus != Node.CONDITION 成立,说明当前node发生中断了...
    9. if (t != null && t.waitStatus != Node.CONDITION) {
    10. //清理条件队列中所有 取消状态的节点
    11. unlinkCancelledWaiters();
    12. //更新局部变量t的引用为最新的队尾节点,因为unlinkCancelledWaiters方法可能会修改尾节点lastWaiter引用
    13. t = lastWaiter;
    14. }
    15. //为当前线程创建node节点,设置状态为condition状态(-2)
    16. Node node = new Node(Thread.currentThread(), Node.CONDITION);
    17. //条件成立:说明条件队列没有任何元素,当前线程是第一个入队元素,让firstWaiter指向当前node
    18. if (t == null)
    19. firstWaiter = node;
    20. //当前条件队列有元素了,做追加操作
    21. else
    22. t.nextWaiter = node;
    23. //更新队尾节点为当前node
    24. lastWaiter = node;
    25. //返回当前线程的node
    26. return node;
    27. }

    6.AQS.fullyRelease

    释放当前线程node占用的所有锁资源逻辑
  • 正常情况会全部释放掉锁资源,有一种情况,那就是未占用锁的线程调用了await方法,这是错误写法,出现了的话,会将当前线程对应的node节点修改为取消状态,那什么时候会将取消状态清除出去?后面节点入队列时候发现队尾节点状态不对,会将取消状态节点清除出去。
  • 如果成功释放了占用的锁,为什么要返回当前线程释放的state值?
  • 因为当线程node被迁移到AQS阻塞队列时,再次被唤醒,且当前node是head.next节点,而且当前锁状态state==0时,当前node可以获取到锁,此时需要将 state 的值设置为savedState ```java

final int fullyRelease(Node node) { //完全释放锁是否成功:当failed失败时,说明当前线程是在未先调用lock方法前提下,直接调用await方法的线程(这是错误的写法) //假设失败,在finally代码块会将刚刚入条件队列的node节点的状态设置为取消状态,后继节点线程 就会将取消状态的节点 给清理出去 boolean failed = true; try { //获取当前线程 所持有的state值 int savedState = getState(); //绝大部分情况下,这里会返回true if (release(savedState)) { //失败标记设置为false failed = false; //返回当前线程释放的state值 //这里为什么要返回savedState? //因为当线程node被迁移到AQS阻塞队列时,再次被唤醒,且当前node是head.next节点,而且当前锁状态state==0时,当前ndoe可以获取到锁, //此时需要将 state 的值设置为savedState return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; } }

  1. <a name="Kig77"></a>
  2. ### 7. AQS.isOnSyncQueue
  3. 判断当前node是否在AQS的阻塞队列,返回true代表已经在阻塞队列,false则不在阻塞队列
  4. - 第一个if判断
  5. - **节点状态==condition(-2)说明节点一定在条件队列里**
  6. - 当前节点的前驱节点为空,首先节点的状态一定不为-2,可能是 0 或者 1,1表示未占用锁的线程调用了await方法,让node节点状态变成 1 取消状态;0说明当前线程已经被signal方法唤醒,因为唤醒操作是先修改节点状态,再迁移到阻塞队列,所以前驱节点为空,说明此时只是节点状态改变了,但还没有入阻塞队列
  7. - 第二个if判断
  8. - 执行到第二个if判断,可以排除掉一种情况,那就是node节点状态==1取消状态这种情况,因为signal方法是不会把取消状态的节点迁移到阻塞队列里去
  9. - **条件成立?为啥一定在阻塞队列里?**
  10. - 因为入阻塞队列是在enq方法,入队逻辑有三步:1.设置node.prev= tail;2.cas当前node为阻塞队列的节点;3.设置前驱的后继节点指向node =》pred.next==当前node
  11. - 所以就算node的前驱不为空,也不能判断node已经在阻塞队列里了,也有可能是正在过程中,而node的后继节点不为空,则表示后面有元素,一定在队列里了,返回true
  12. - 执行到findNodeFromTail 这里,说明当前node可能已经在队列里了,只是后面没有后继节点,因此调用findNodeFromTail方法从后向前查找,**找到了返回true,找不到说明不在阻塞队列里**
  13. ```java
  14. //判断当前node是否在AQS的阻塞队列
  15. final boolean isOnSyncQueue(Node node) {
  16. //条件一node.waitStatus == Node.CONDITION成立
  17. //说明当前node一定在条件队列,因为singal方法迁移node节点到 阻塞队列时,会将node节点状态设置为0
  18. //前置条件:状态不为condition
  19. //node.waitStatus == 0 (表示当前节点已经被signal了)
  20. //node.waitStatus == 1 (当前线程是未持有锁调用的await方法...进入到该方法前状态被修改为取消状态)
  21. //node.waitStatus == 0 为什么还要判断node.prev ==null?
  22. //因为signal方法 是先修改状态,再迁移
  23. if (node.waitStatus == Node.CONDITION || node.prev == null)
  24. return false;
  25. //执行到这里,node.waitStatus!=condition&&node.prev!=null可以排除 node.waitStatus==1 取消状态
  26. //为什么可以排除 取消状态? 因为signal方法是不会把 取消状态的node节点迁移到 阻塞队列里的
  27. //设置prev引用的逻辑是 迁移 阻塞队列设置的enq()
  28. //入队逻辑:1.设置node.prev =tail;2. cas当前node为阻塞队列尾节点,cas成功才算真正入阻塞队列;3.pred.next=node
  29. //可以推算出,就算prev不为nul,也不能说明node已经成功入阻塞队列了
  30. //条件成立,说明当前节点后面有元素了,那么已经迁移到阻塞队列了
  31. if (node.next != null)
  32. return true;
  33. //执行到这里,说明当前节点的状态是node.prev!=null &&node.waitStatus == 0
  34. //findNodeFromTail:从AQS的阻塞队列尾开始向前查找node,查找到返回true,找不到返回false
  35. return findNodeFromTail(node);
  36. }

8. Condition.signal

  • 先判断当前线程是不是占用锁的线程,不是直接抛出异常
  • 获取条件队列的头节点,如果不为空,表示队列有元素,调用doSignal唤醒条件队列的第一个节点

    1. public final void signal() {
    2. //判断signal方法的线程是否是独占锁的线程,如果不是,那么抛出异常
    3. if (!isHeldExclusively())
    4. throw new IllegalMonitorStateException();
    5. //获取条件队列的第一个节点
    6. Node first = firstWaiter;
    7. //如果首节点不为空
    8. if (first != null)
    9. //唤醒条件队列的首节点线程
    10. doSignal(first);
    11. }

    9. Condition.doSignal

    唤醒条件队列的首个节点逻辑

  • 如果头节点的下一个节点为空,说明头节点出队列之后,头尾节点都为空,也就是条件队列没有元素了

  • 断开当前头节点的next
  • 调用transferForSignal 将当前头节点迁移到阻塞队列里,返回true代表迁移成功,false说明迁移失败
  • 循环的条件是:如果头节点迁移失败,那么会将头节点设置为头节点的下一个节点继续尝试,直至迁移一个节点成功,或者头节点为空
  • 什么时候头节点迁移失败?

    • node节点状态是取消状态(非占用锁的线程调用await方法)
    • node对应的线程在挂起期间被其他线程使用中断信号唤醒,这时候也会主动迁移到阻塞队列里,这时节点状态也会修改为0

      1. private void doSignal(Node first) {
      2. do {
      3. //firstWaiter = first.nextWaiter 因为first马上要出队列了,让firstWaiter指向下一个节点
      4. //如果当前节点的下一个节点为null,说明只有这一个节点,当前节点出队后,需要修改lastWaiter为null
      5. if ( (firstWaiter = first.nextWaiter) == null)
      6. lastWaiter = null;
      7. //当前first节点 出条件队列,断开与下一个节点的关系
      8. first.nextWaiter = null;
      9. //transferForSignal true 表示当前first迁移到阻塞队列成功,false说明迁移失败,
      10. //while循环:(first = firstWaiter) != null 当前first节点迁移失败,将当前first更新为first.next继续尝试迁移,会继续向后尝试迁移节点,直到迁移一个节点成功,或者条件队列为null为止
      11. } while (!transferForSignal (first) &&
      12. (first = firstWaiter) != null);
      13. }

      10. AQS.transferForSignal

      迁移node节点到阻塞队列逻辑,迁移成功返回true,迁移失败返回false
      通常返回true,什么时候返回false?

  1. node节点状态是取消状态(非占用锁的线程调用await方法)
  2. node对应的线程在挂起期间被其他线程使用中断信号唤醒,这时候也会主动迁移到阻塞队列里,这时节点状态也会修改为0
  • 第一步:先cas方式将node状态从-2 修改为0 ,设置成功后才继续往下,那什么时候会失败呢?
  • node状态是cancel取消状态(非占用锁的线程调用await方法)或者node对应的线程在挂起期间被其他线程使用中断信号唤醒,这时候也会主动迁移到阻塞队列里,这时节点状态也会修改为0
  • cas成功后第二步:enq方法将node入阻塞队列,返回node的前驱节点
  • 第三步:
    • 条件一:前驱节点的状态> 0,说明前驱节点状态为 取消状态
    • 条件二:前置条件:前驱节点状态<=0,cas修改前驱节点状态为 -1 signal状态失败
  • 条件一或者条件二满足一个 都将唤醒刚入队的node对应的线程
    1. final boolean transferForSignal(Node node) {
    2. //cas修改当前node节点状态为0,因为当前节点 之后要迁移到AQS的阻塞队列了
    3. //成功:当前节点在条件队列中状态正常
    4. //失败:1.取消状态(线程await方法时候,未持有锁,最终线程对应的node会被设置为cancel 取消状态)
    5. // 2.node对应的线程 挂起期间,被其他线程使用 中断信号唤醒过...(就会主动进入到阻塞队列,这时也会修改状态为0)
    6. if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
    7. return false;
    8. //将node入AQS的阻塞队列,返回node的前驱节点
    9. Node p = enq(node);
    10. //获取前驱节点的状态
    11. int ws = p.waitStatus;
    12. //条件一:成立 说明前驱节点的状态在阻塞队列里是取消状态,唤醒当前节点
    13. //条件二:前置条件:ws<=0
    14. //compareAndSetWaitStatus(p, ws, Node.SIGNAL) 返回true 表示设置前驱节点为signal状态成功
    15. //compareAndSetWaitStatus(p, ws, Node.SIGNAL) 返回false =>什么时候返回false?
    16. //当前驱node对应的线程是 lockInterrupt入队的node时候,是会响应中断的,外部线程给前驱线程中断信号后,前驱节点会将状态修改为取消状态,并执行出队逻辑
    17. //前驱node状态只要不是 0 或者 -1 就会唤醒当前线程
    18. if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
    19. //唤醒当前node对应的线程
    20. LockSupport.unpark(node.thread);
    21. return true;
    22. }
    执行完dosignal方法后,node就从条件队列迁移到阻塞队列里了,会判断其前驱节点的 waitStatus,如果大于0(是取消状态)或者设置状态为-1 (signal状态),这个时候会直接唤醒node,否则就按AQS的正常情况,竞争锁的逻辑

11. Condition.await

在上面分析await方法时候,线程会被添加到条件队列,释放锁,在循环中被阻塞。而通过其他线程调用signal方法,将线程从条件队列唤醒之后,退出循环,继续后面逻辑。

在循环里有 一个方法,checkInterruptWhileWaiting 这个方法是做什么的呢?顾名思义:判断线程在条件队列阻塞期间,有没有被其他线程使用中断信号唤醒过
如果线程阻塞期间被中断唤醒,也会将节点迁移到阻塞队列中,参与AQS竞争,对应 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 条件成立

  • 在参与竞争期间仍被中断唤醒,判断中断模式是不是 THROW_IE,不是则代表该node在阻塞期间被中断唤醒过,只需要重新中断响应即可
  • 如果node的后继节点不为空,说明当前node是在条件队列里被中断唤醒,这种情况下虽然node被迁移到了阻塞队列里,但是并没有断开与后继节点的联系,会进行一次全队的清理工作,清除取消状态节点
  • 最后判断中断模式,如果是在条件队列被中断唤醒 抛出异常,在阻塞队列被中断唤醒

    1. public final void await() throws InterruptedException {
    2. //判断当前线程是否已经是中断状态,如果是直接抛出中断异常
    3. if (Thread.interrupted())
    4. throw new InterruptedException();
    5. //将调用await方法的线程包装成node并加入到条件队列,并返回当前线程的node
    6. Node node = addConditionWaiter();
    7. //完全释放掉当前线程对应的锁(将state置为0)
    8. //为什么要释放锁呢? 自己加着锁,然后挂起后,谁还能来唤醒你呢
    9. int savedState = fullyRelease(node);
    10. //0 在condition队列挂起期间未接收过中断信号
    11. //-1 在condition队列挂起期间接收到中断信号了
    12. //1 在condition队列挂起期间未接收到中断信号,但是在迁移到 AQS的阻塞队列 之后,接收过中断信号
    13. int interruptMode = 0;
    14. //isOnSyncQueue方法返回true 表示当前线程已经迁移到 AQS的阻塞队列了
    15. //返回false 说明当前node还在条件队列中,需要继续park
    16. while (!isOnSyncQueue(node)) {
    17. //挂起当前node对应的线程
    18. LockSupport.park(this);
    19. //什么时候会被唤醒?
    20. //1.正常情况,当前线程被其他线程调用singal方法转移到阻塞队列,然后获取到锁之后,会唤醒
    21. //2.迁移到阻塞队列中,发现阻塞队列中的前驱节点状态 是取消状态或者修改前驱节点状态-1失败,会唤醒当前节点
    22. //3.当前节点在等待队列被park期间,被外部线程中断唤醒
    23. //checkInterruptWhileWaiting:就算在condition条件队列挂起期间 线程发生中断了,对应的node也会被迁移到 AQS的阻塞队列
    24. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
    25. break;
    26. }
    27. //执行到这里,说明当前node已经迁移到 AQS的阻塞队列了
    28. //acquireQueued(node, savedState) 条件成立,表示在阻塞队列中被中断唤醒过
    29. //interruptMode != THROW_IE 成立 说明当前node在条件队列内未发生过中断唤醒
    30. //条件成立:说明当前线程在阻塞队列竞争锁的时候发生中断, && 中断标识位不是 THROW_IE
    31. if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    32. //将当前线程的中断标识位设置为重新中断
    33. interruptMode = REINTERRUPT;
    34. //条件成立,代表当前node在条件队列中被中断唤醒了,会加入到阻塞队列,但是并未设置node.nextWaiter==null
    35. if (node.nextWaiter != null)
    36. //清理条件队列内取消状态节点,让取消状态节点出队列
    37. unlinkCancelledWaiters();
    38. //条件成立,说明挂起期间被中断过 1条件队列被中断,2条件队列之外被中断
    39. if (interruptMode != 0)
    40. reportInterruptAfterWait(interruptMode);
    41. }

    12. Condition.checkInterruptWhileWaiting

如果被中断过了,需要调用 transferAfterCancelledWait方法去判断 是在条件队列里被中断唤醒还是在阻塞队列里被中断唤醒的,根据这个然后觉得 抛出中断异常 还是 重新中断

  • 返回 THROW_IE 表示在条件队列里阻塞过程中被其他线程中断唤醒
  • 返回 REINTERRUPT 表示在阻塞队列里被中断唤醒,也就是其他线程先调用signal方法唤醒node,入阻塞队列之后在等待竞争锁的时候,node被其他线程中断唤醒了

    1. private int checkInterruptWhileWaiting(Node node) {
    2. //Thread.interrupted()返回当前线程中断标记位,并且重置当前标记为false
    3. return Thread.interrupted() ?
    4. //如果是被中断唤醒了,需要判断是在条件队列里面被中断唤醒 还是在阻塞队列里面被中断唤醒
    5. (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
    6. 0;
    7. }

    13. AQS.transferAfterCancelledWait

    返回true表示当前节点是在条件队列里面被中断唤醒,false表示当前节点不是在条件队列里被中断唤醒
    如果cas节点状态成功,会把节点迁移到阻塞队列里,但并没有将node的后继节点断开,正常通过signal方法唤醒的线程,会迁移到阻塞队列里并断开与后继节点的联系。

    1. //返回true表示当前节点是在条件队列里面被中断唤醒,false表示当前节点不是在条件队列里被中断唤醒
    2. final boolean transferAfterCancelledWait(Node node) {
    3. //条件成立,代表cas修改节点状态成功,说明当前node一定在条件队列内,因为调用signal方法迁移节点到阻塞队列时,会将node节点从-2修改为0
    4. //这里有个知识点,阻塞的线程被唤醒,不一定是通过lockSupport.unpark方式唤醒,也可能通过线程.interrupt()方法更新了中断标识,并唤醒了被阻塞的线程
    5. if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    6. //在条件队列中阻塞被中断唤醒的node,也会被迁移到阻塞队列里面
    7. enq(node);
    8. //返回true表示 在条件队列里面被中断唤醒的
    9. return true;
    10. }
    11. //如果cas失败,则判断node是否已经在阻塞队列里
    12. //执行到这里 几种情况?
    13. //1.当前node已经被外部线程调用signal方法 迁移到阻塞队列了
    14. //2.当前node正在被外部线程调用signal方法 迁移到阻塞队列中
    15. while (!isOnSyncQueue(node))
    16. Thread.yield();
    17. //返回false 表示当前节点 被中断唤醒时候,不在条件队列了
    18. return false;
    19. }

    14. AQS.reportInterruptAfterWait

    根据中断模式进行处理,是 THROW_IE 直接抛出中断异常,否则设置当前线程中断标记位为true,如果程序响应中断,走响应中断程序;不响应中断,什么都不操作

    1. private void reportInterruptAfterWait(int interruptMode)
    2. throws InterruptedException {
    3. //条件成立:说明是在条件队列中被中断,直接抛出异常
    4. if (interruptMode == THROW_IE)
    5. throw new InterruptedException();
    6. //条件成立:说明是在条件队列之外被中断,设置当前线程中断标记位为true,如果程序是不响应中断的,那么程序是不会响应中断的
    7. else if (interruptMode == REINTERRUPT)
    8. selfInterrupt();
    9. }

    总结

    线程A先通过lock.lock方法获取锁成功,然后调用await方法 将线程封装为node添加到条件队列,释放线程A所有锁,进入循环判断node是否在AQS阻塞队列,没有则阻塞线程A;另外一个线程B 通过lock.lock方法获取到锁之后,调用了signal或siganlAll方法,将条件队列的节点迁移到阻塞队列,使得线程A有机会进入到阻塞队列,从而退出循环,然后参与AQS的竞争锁逻辑。如果线程A在最开始lock.lock方法获取锁失败则会直接添加到AQS的阻塞队列,参与AQS的竞争锁逻辑。

  • await:阻塞-》将线程封装为node节点,释放锁资源,循环里阻塞线程,被其他线程调用signal、siganlAll方法,迁移到阻塞队列后,退出循环,参与AQS竞争锁逻辑
  • signal:释放-》从条件队列的头节点开始向后,先cas修改node节点状态为 0 然后迁移到AQS阻塞队列返回node的前驱节点,如果前驱节点是取消状态 或者cas 状态为 -1 signal失败,则LockSupport.unpark方式唤醒该node节点