线程等待唤醒机制

wait/notify

  1. Object类中的wait和notify方法实现线程等待和唤醒
  2. wait和notify方法必须要在同步块或者方法里面,且成对出现使用
  3. 先wait后notify才OK ```java public class LockSupportDemo { public static void main(String[] args) throws InterruptedException {

    1. Object objectLock = new Object(); //同一把锁,类似资源类
    2. new Thread(() -> {
    3. synchronized (objectLock) {
    4. try {
    5. objectLock.wait();
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. }
    10. System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒了");
    11. }, "t1").start();

//暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }

  1. new Thread(() -> {
  2. synchronized (objectLock) {
  3. objectLock.notify();
  4. }
  5. }, "t2").start();
  6. }

}

  1. <a name="P7HaU"></a>
  2. ### await/signal
  3. 1. Condition接口中的await后signal方法实现线程的等待和唤醒
  4. 1. Condtion中的线程等待和唤醒方法之前,需要先获取锁
  5. 1. 一定要先await后signal,不要反了
  6. ```java
  7. public class LockSupportDemo2
  8. {
  9. public static void main (String[]args)
  10. {
  11. Lock lock = new ReentrantLock();
  12. Condition condition = lock.newCondition();
  13. new Thread(() -> {
  14. lock.lock();
  15. try {
  16. System.out.println(Thread.currentThread().getName() + "\t" + "start");
  17. condition.await();
  18. System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. } finally {
  22. lock.unlock();
  23. }
  24. }, "t1").start();
  25. //暂停几秒钟线程
  26. try {
  27. TimeUnit.SECONDS.sleep(3L);
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. new Thread(() -> {
  32. lock.lock();
  33. try {
  34. condition.signal();
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. } finally {
  38. lock.unlock();
  39. }
  40. System.out.println(Thread.currentThread().getName() + "\t" + "通知了");
  41. }, "t2").start();
  42. }
  43. }

Object和Condition使用的限制条件

  1. 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
  2. 必须要先等待后唤醒,线程才能够被唤醒

    LockSupport

  3. LockSupport是用来创建锁和共他同步类的基本线程阻塞原语。

  4. LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
  • park() /park(Object blocker) :permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为零并返回。
  • unpark(Thread thread) :调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
  1. 之前错误的先唤醒后等待,LockSupport照样支持 ```java //正常使用+不需要锁块
    1. Thread t1 = new Thread(() -> {
    2. System.out.println(Thread.currentThread().getName() + " " + "1111111111111");
    3. LockSupport.park();
    4. System.out.println(Thread.currentThread().getName() + " " + "2222222222222------end被唤醒");
    5. }, "t1");
    6. t1.start();

//暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

  1. LockSupport.unpark(t1);
  2. System.out.println(Thread.currentThread().getName() + " -----LockSupport.unparrk() invoked over");
  1. <a name="QgfPl"></a>
  2. ## 线程中断:
  3. 参考:[https://www.zhihu.com/question/41048032/answer/89431513](https://www.zhihu.com/question/41048032/answer/89431513)<br />interrupted()是Java提供的一种中断机制,要把中断搞清楚,还是得先系统性了解下什么是中断机制。
  4. <a name="XkYot"></a>
  5. ### 什么是中断?
  6. 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——中断。
  7. - 中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。若要中断一个线程,你需要手动调用该线程的interrupted方法,该方法也仅仅是将线程对象的中断标识设成true;接着你需要自己写代码不断地检测当前线程的标识位;如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。
  8. - 每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
  9. - 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
  10. <a name="wMnZj"></a>
  11. ### 中断的相关方法
  12. - public void interrupt() <br />将调用者线程的中断状态设为true。
  13. - public boolean isInterrupted() <br />判断调用者线程的中断状态。
  14. - public static boolean interrupted <br />只能通过Thread.interrupted()调用。 <br />它会做两步操作:
  15. 1. 返回**当前线程**的中断状态;
  16. 1. 将当前线程的中断状态设为false;
  17. <a name="Nk40m"></a>
  18. ## AQS
  19. AbstractQueuedSynchronizer 抽象队列同步器。是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量 表示持有锁的状态。这里的同步器组件指[CountDownLatch](https://so.csdn.net/so/search?q=CountDownLatch&spm=1001.2101.3001.7020)、Semaphore、ReentrantLock、ReentrantReadWriteLock 等。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651495478047-1c80f248-bc1c-47b7-87b1-055ec421433e.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=378&id=ud5f6cde6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=378&originWidth=1068&originalType=binary&ratio=1&rotation=0&showTitle=false&size=144652&status=done&style=none&taskId=u9ad6c06c-a142-41b3-90f2-ecf6ff579b7&title=&width=1068)
  20. <a name="gIQgW"></a>
  21. ### **锁和同步器的关系**
  22. - 锁:面向锁的使用者(定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可)
  23. - 同步器:面向锁的实现者(比如Java并发大神Douglee,提出统一规 范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。)
  24. <a name="oLtgU"></a>
  25. ### 能干嘛
  26. - 加锁会导致阻塞,有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
  27. - 抢到资源的线程直接使用办理业务,抢占不到资源的线程的必然涉及一种排队等候机制,抢占资源失败的线程继续去等待(类似办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
  28. - 既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
  29. - 如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的 结点(Node),通过CAS、自旋以及LockSuport.park()的方式,维护state变量的状态,使并发达到同步的效果。
  30. - ![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651495787283-43dc988e-7eae-4269-a409-591b13384b1a.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u7352fcf2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=506&originWidth=1122&originalType=url&ratio=1&rotation=0&showTitle=false&size=389668&status=done&style=none&taskId=u532932a2-b176-4a2d-a56d-41416538378&title=)
  31. AQS使用一个volatile的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成 一个 Node节点 来实现锁的分配,通过**CAS**完成对**State**值的修改。![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651495828895-0c1b5d51-a26d-4a51-875a-8164385460d6.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u1ea7799f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=702&originWidth=983&originalType=url&ratio=1&rotation=0&showTitle=false&size=79543&status=done&style=none&taskId=u0a3e8ae9-8474-4352-ab0f-64bff4ece60&title=)<br />
  32. <a name="UKzDh"></a>
  33. ### AQS内部体系架构![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651495853289-931a4691-1528-4116-a4a5-ee5e237d919e.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u9348e1f4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=503&originWidth=1127&originalType=url&ratio=1&rotation=0&showTitle=false&size=189205&status=done&style=none&taskId=ua327556d-adc3-4208-b43a-18a0c9fd1ec&title=)
  34. <a name="NXY2a"></a>
  35. #### AQS的int变量:
  36. AQS的同步状态State成员变量<br />类比,银行办理业务的受理窗口状态:
  37. - 零就是没人,自由状态可以办理
  38. - 大于等于1,有人占用窗口,等着去
  39. ```java
  40. /**
  41. ● The synchronization state.
  42. */
  43. private volatile int state;

AQS的CLH队列

CLH队列(三个大牛的名字组成),为一个双向队列
类比,银行侯客区的等待顾客
image.png

小总结:

  • 有阻塞就需要排队,实现排队必然需要队列
  • state变量+CLH双端Node队列

    内部类Node(Node类在AQS类内部)

    Node的int变量

    Node的等待状态waitState成员变量(注意与status状态区分,status表示同步状态)
    类比,等候区其它顾客(其它线程)的等待状态
    队列中每个排队的个体就是一个Node.
    1. volatile int waitStatus;

    Node此类的讲解

    内部结构:image.png
    属性说明:
    image.png

    总结

    image.png

    从我们的ReentrantLock开始解读AQS

    ReentrantLock:Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的 ```java package com.example.demo.juc;

import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest { public static void main(String[] args) {

  1. ReentrantLock lock = new ReentrantLock();

//带入一个银行办理业务的案例来模拟我们的AQS如何进行线程的管理和通知唤醒机制

//3个线程模拟3个来银行网点,受理窗口办理业务的顾客

//A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理 new Thread(() -> { lock.lock(); try { System.out.println(“——-A thread come in”);

  1. try {
  2. TimeUnit.MINUTES.sleep(20);
  3. } catch (Exception e) {
  4. e.printStackTrace();
  5. }
  6. } finally {
  7. lock.unlock();
  8. }
  9. }, "A").start();

//第二个顾客,第二个线程—-》由于受理业务的窗口只有一个(只能一个线程持有锁),此时B只能等待, //进入候客区 new Thread(() -> { lock.lock(); try { System.out.println(“——-B thread come in”); } finally { lock.unlock(); } }, “B”).start();

//第三个顾客,第三个线程—-》由于受理业务的窗口只有一个(只能一个线程持有锁),此时C只能等待, //进入候客区 new Thread(() -> { lock.lock(); try { System.out.println(“——-C thread come in”); } finally { lock.unlock(); } }, “C”).start(); } }

  1. **初始状态:**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651499377505-91041441-4950-4516-83c3-5fda1d12b38d.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ue3d9bce4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=495&originWidth=954&originalType=url&ratio=1&rotation=0&showTitle=false&size=221671&status=done&style=none&taskId=uc0d96078-2a29-4f64-bb27-07d9874c374&title=)
  2. **lock():**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651498970344-c32b0920-3741-4fdd-a6be-1e386bf0b7fb.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=555&id=ub1921105&margin=%5Bobject%20Object%5D&name=image.png&originHeight=555&originWidth=976&originalType=binary&ratio=1&rotation=0&showTitle=false&size=81161&status=done&style=none&taskId=u1cf3394b-9251-4a26-88fb-0b7c2ae55e5&title=&width=976)<br />**compareAndSetState(0, 1):**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651499049883-cd94d7f9-5f67-41ba-84ac-d52d71ecd85f.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=296&id=ua65c5b18&margin=%5Bobject%20Object%5D&name=image.png&originHeight=296&originWidth=1267&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52605&status=done&style=none&taskId=u5a2f427c-6351-4562-a047-03bfe7d873a&title=&width=1267)<br />该方法会用CAS修改state的值,state默认值是0,所以第一次修改会成功,也就是A线程会修改成功并返回true
  3. **setExclusiveOwnerThread(Thread.currentThread());**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651499190859-31761976-7ef8-4529-806e-9d9baf2c9439.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=213&id=u1e151017&margin=%5Bobject%20Object%5D&name=image.png&originHeight=213&originWidth=1163&originalType=binary&ratio=1&rotation=0&showTitle=false&size=38556&status=done&style=none&taskId=u2e0b9877-7e43-4334-ab8c-31584e9b896&title=&width=1163)<br />该方法当前线程赋值给这个属性,这个属性代表拥有这把锁的线程,所以是A线程拥有这把锁,加锁成功。<br />**这是此时的状态:也就是第一个顾客来之后:**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651499413283-b7b3da14-a4ae-42aa-acea-de794287e3ad.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u0f693f13&margin=%5Bobject%20Object%5D&name=image.png&originHeight=494&originWidth=1081&originalType=url&ratio=1&rotation=0&showTitle=false&size=300242&status=done&style=none&taskId=u27545057-8830-463f-ac53-fd69a1ff002&title=)<br />接下来是B线程:<br />因为此时state=1,所以**compareAndSetState(0, 1) **会修改失败,返回false,执行acquire(1);<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651499817566-6092a992-5b06-4048-8156-4be6f6a6d271.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=199&id=u3ce05079&margin=%5Bobject%20Object%5D&name=image.png&originHeight=199&originWidth=852&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27491&status=done&style=none&taskId=ub933f61f-ce04-4623-b814-ccdf6e35806&title=&width=852)<br />首先执行tryAcquire(arg):这个方法是尝试获取锁,成功返回true,失败返回false![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651499934002-36991868-32f6-4685-8a3b-ff7e1ca0a631.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=103&id=ue2d33545&margin=%5Bobject%20Object%5D&name=image.png&originHeight=103&originWidth=879&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18701&status=done&style=none&taskId=u3a651594-f589-47ee-9a0d-2888d831a2a&title=&width=879)<br />里面调用nonfairTryAcquire(acquires);<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651500117977-19be4df0-f8cf-403a-a02f-3365e89b83bc.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=588&id=u6e9153f1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=588&originWidth=1155&originalType=binary&ratio=1&rotation=0&showTitle=false&size=93318&status=done&style=none&taskId=u6ffabc16-4b8b-4814-ab04-871817cc12d&title=&width=1155)
  4. 此时c=getState(),c=1;getExclusiveOwnerThread()=ThreadA;所以该方法返回false,尝试加锁失败
  5. 接着会走到addWaiter(Node.**_EXCLUSIVE_**):Node.**_EXCLUSIVE_**初始化为nulltail初始化也是null;所以执行enq(node)方法![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651500289507-b3ecd243-cfd3-4e2d-8601-c5e47fa70c3d.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=469&id=u1e23cd29&margin=%5Bobject%20Object%5D&name=image.png&originHeight=469&originWidth=936&originalType=binary&ratio=1&rotation=0&showTitle=false&size=55625&status=done&style=none&taskId=u07f89708-221b-4834-a2a5-1c60fe6af17&title=&width=936)<br />**注意:这是一个自旋操作,**<br />tail为空节点,会进入if判断,通过CAS操作设置head头结点的指向Node空节点(此时Node节点即图中的傀儡节点,不储存数据,仅用于占位)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651500678737-f4efa58c-a9b6-418d-82f6-61e6e3557d71.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=479&id=u8b6f41bd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=479&originWidth=1021&originalType=binary&ratio=1&rotation=0&showTitle=false&size=55272&status=done&style=none&taskId=ub9b01263-e830-420a-8109-bd122f50e8a&title=&width=1021)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651500814116-ba00abf8-e942-45f5-ac53-8c3a868868ac.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=153&id=ua41e0dc3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=153&originWidth=1007&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25045&status=done&style=none&taskId=u215bf7bd-2128-4d10-b73a-23062d22e12&title=&width=1007)<br />**此时状态:**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651500885369-51e4ce10-9d34-464f-b10d-95dacee1b7f7.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=460&id=u6d73ca33&margin=%5Bobject%20Object%5D&name=image.png&originHeight=460&originWidth=928&originalType=binary&ratio=1&rotation=0&showTitle=false&size=189393&status=done&style=none&taskId=u3983d3f7-dc20-4383-a2af-d5ca34fe824&title=&width=928)<br />然后再将head头结点的执行赋给tail尾结点的指向<br />`tail=head; `<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651501003099-6649e425-a30b-4bf4-b0da-3325d46a52e7.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=454&id=ucf8a71e0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=454&originWidth=933&originalType=binary&ratio=1&rotation=0&showTitle=false&size=181347&status=done&style=none&taskId=uf8bb2c75-993d-4fbf-916a-f2f837a2e94&title=&width=933)<br />完成后,不会走下面的else 分支。由于是自旋,继续从头开始<br />tail不为null,走else分支,
  6. 首先:
  7. node.prev = t;//将B线程的前指针指向空节点<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651501106804-a55430b6-636b-4a9b-94b8-c364a4bdef64.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=452&id=u993618ea&margin=%5Bobject%20Object%5D&name=image.png&originHeight=452&originWidth=931&originalType=binary&ratio=1&rotation=0&showTitle=false&size=209257&status=done&style=none&taskId=u3b719598-0b28-4e95-896b-d43b17e1622&title=&width=931)
  8. 然后:<br />compareAndSetTail(t, node) //设置尾结点:将tail尾结点所执向的节点改为执向顾客B<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651501127337-12ba9099-a55f-463c-b708-38ada6384c44.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=454&id=udee0b7fc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=454&originWidth=933&originalType=binary&ratio=1&rotation=0&showTitle=false&size=220132&status=done&style=none&taskId=u802f1ff9-3dd7-4626-84b6-933816ac0e5&title=&width=933)
  9. 然后:<br />t.next = node; //将空节点的next指针指向顾客B<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651501142637-d96111ca-9554-4eaa-b934-b15514f3bd5c.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=456&id=uce9fba9a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=456&originWidth=936&originalType=binary&ratio=1&rotation=0&showTitle=false&size=207709&status=done&style=none&taskId=u99424a57-a6b8-4bb2-a058-8edf68a6f5a&title=&width=936)<br />最后:return结束自旋!<br />然后再执行这个方法:acquireQueued(addWaiter(Node.**_EXCLUSIVE_**), arg))<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651502959057-427b5372-a9d0-4e50-bae9-83071b33cca0.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=687&id=u0f98c92b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=687&originWidth=1455&originalType=binary&ratio=1&rotation=0&showTitle=false&size=148876&status=done&style=none&taskId=ua2e4d6ca-b882-4c40-9a74-237dc04af17&title=&width=1455)![image.png](https://cdn.nlark.com/yuque/0/2022/png/21376575/1651503026575-13783486-f765-4c50-896d-818251ac2543.png#clientId=u8ad1fab5-ccff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=141&id=u250e3e50&margin=%5Bobject%20Object%5D&name=image.png&originHeight=141&originWidth=670&originalType=binary&ratio=1&rotation=0&showTitle=false&size=19982&status=done&style=none&taskId=u73a299be-deed-470a-81a8-0285d8d871d&title=&width=670)<br />释放锁的代码就不看了,非常简单:
  10. ```java
  11. LockSupport.unpark(s.thread);

image.pngimage.png
B线程获取到了锁,原来的B节点变成了原来的哨兵节点(因为C跟B的逻辑想差不大,因此没有过多赘述)