3.JUC【Java面试第三季】
3.JUC【Java面试第三季】
- 前言
- 推荐
- 3.JUC
- 06_闲聊AQS面试
- 1.题目+说明
- 07_可重入锁理论
- 2.可重入锁
- 说明
- “可重入锁”这四个字分开来解释
- 可重入锁的种类
- 08_可重入锁的代码验证-上
- 09_可重入锁的代码验证-下
- 3.LockSupport
- 10_LockSupport是什么
- LockSupport是什么
- 11_waitNotify限制
- ==线程等待唤醒机制(wait/notify)==
- 3种让线程等待和唤醒的方法
- Object类中的wait和notify方法实现线程等待和唤醒
- 代码
- 小总结
- 12_awaitSignal限制
- Condition接口中的await后signal方法实现线程的等待和唤醒
- 代码
- 小总结
- ==传统的synchronized和lLock实现等待唤醒通知的约束==
- 13_LockSupport方法介绍
- LockSupport类中的park等待和unpark唤醒
- 是什么
- 主要方法
- 14_LockSupport案例解析
- 代码
- 重点说明(重要)
- 面试题
- 15_AQS理论初步
- 4.AbstractQueuedSynchronizer之AQS
- 先从字节跳动及其它大厂面试题说起前置知识
- 前置知识
- 是什么
- 16_AQS能干嘛
- AQS为什么是JUC内容中最重要的基石
- 和AQS有关的
- 进一步理解锁和同步器的关系
- 能干嘛
- 17_AQS源码体系-上
- AQS初步
- AQS初识
- 18_AQS源码体系-下
- AQS内部体系架构
- AQS自身
- AbstractQueuedSynchronizer内部类
- AQS同步队列的基本结构
- 19_AQS源码深度解读01
- 从我们的ReentrantLock开始解读AQS
- Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的
- ReentrantLock的原理
- 从最简单的lock方法开始看看公平和非公平
- 非公平锁走起,方法lock()
- 20_AQS源码深度解读02
- 21_AQS源码深度解读03
- 22_AQS源码深度解读04
- 23_AQS源码深度解读05
- 24_AQS源码深度解读06
- 25_AQS源码深度解读07
- 方法unlock()
- 26_AQS小总结
- 最后
前言
2023-2-1 14:57:23
以下内容源自
【尚硅谷Java大厂面试题第3季,跳槽必刷题目+必扫技术盲点(周阳主讲)-哔哩哔哩】
仅供学习交流使用
推荐
Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)
3.JUC
06_闲聊AQS面试
1.题目+说明
07_可重入锁理论
2.可重入锁
说明
可重入锁又名递归锁
是指在同一个线程在外层方法获取锁的时候,再进入该线程的的内层方法会自动获取锁(前提是锁对象得是同一个对象)
不会因为之前已经获取过还没释放而阻塞。
Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
“可重入锁”这四个字分开来解释
-
可:可以
-
重:再次
-
入:进入
-
锁:同步锁
-
进入什么?
- 进入同步域(即同步代码块/方法或显示锁锁定的代码)
-
一句话
- 一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。
- 自己可以获取自己的内部锁。
可重入锁的种类
- 隐式锁(即synchronized关键字使用的锁)默认是可重入锁。
- 同步块
- 同步方法
08_可重入锁的代码验证-上
同步代码块
package juc;/*** 可重入锁;可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。** 在一个synchronized修饰的方法或代码块的内部* 调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的*/
public class ReEnterLockDemo {static Object objectA=new Object();public static void m1() {new Thread(()->{synchronized (objectA){System.out.println(Thread.currentThread().getName()+"\t"+"-----外层调用");synchronized (objectA){System.out.println(Thread.currentThread().getName()+"\t"+"-----中层调用");synchronized (objectA){System.out.println(Thread.currentThread().getName()+"\t"+"-----内层调用");}}}},"t1").start();}public static void main(String[] args) {m1();}
}
输出结果:
t1 -----外层调用
t1 -----中层调用
t1 -----内层调用
09_可重入锁的代码验证-下
同步方法
package juc;/*** 可重入锁;可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。** 在一个synchronized修饰的方法或代码块的内部* 调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的*/
public class ReEnterLockDemo {public synchronized void m1() {System.out.println("=====外");m2();}public synchronized void m2(){System.out.println("=====中");m3();}public synchronized void m3(){System.out.println("=====内");}public static void main(String[] args) {new ReEnterLockDemo().m1();}
}
- Synchronized的重入的实现机理。
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
- 显式锁(即Lock)也有ReentrantLock这样的可重入锁。
package juc;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的*/
public class ReEnterLockDemo {static Lock lock=new ReentrantLock();public static void main(String[] args) {new Thread(()->{lock.lock();lock.lock();try {System.out.println("======外层");lock.lock();try {System.out.println("======内层");} finally {lock.unlock();}} finally {//这里故意注释,实现加锁次数和释放次数不一样//由于加锁次数和释放次数不一样,第二个线程始终无法得到锁,导致一直在等待lock.unlock();lock.unlock();//正常情况,加锁几次就要解锁几次}},"t1").start();new Thread(()->{lock.lock();try {System.out.println(Thread.currentThread().getName()+"\t"+"-----调用开始");} finally {lock.unlock();}},"t2").start();}
}
3.LockSupport
10_LockSupport是什么
1 为什么要学习LockSupport?1.1 Java----JVM1.2 JUC----AQS---->(前置知识可重入锁、 LockSupport)
2学习方法2.1是什么?2.2能干嘛2.3去哪下2.4怎么玩
3 AB----》after [ before
synchronized-wait-notify
lock-await-signal
LockSupport-park-unpark
LockSupport是什么
LockSupport Java doc
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport中的park()和 unpark()的作用分别是阻塞线程和解除阻塞线程。
总之,比wait/notify,await/signal更强。
Class LockSupport
- 是什么
11_waitNotify限制
线程等待唤醒机制(wait/notify)
3种让线程等待和唤醒的方法
- 方式1:使用Object中的wait()方法让线程等待,使用object中的notify()方法唤醒线程
- 方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
- 方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
Object类中的wait和notify方法实现线程等待和唤醒
代码
- 正常
package juc;public class LockSupportDemo {static Object objectLock =new Object();public static void main(String[] args) {new Thread(()->{synchronized (objectLock){System.out.println(Thread.currentThread().getName()+"\t"+"------come in");try {objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t"+"------被唤醒");}},"A").start();new Thread(()->{synchronized (objectLock){objectLock.notify();System.out.println(Thread.currentThread().getName()+"\t"+"------通知");}},"B").start();}
}
- 异常1
package juc;/*** 要求:t1线程等待3秒钟,3秒钟后t2线程唤龌t1线程继续工作* 以下异常情况:* 2wait方法和notify方法,两个都去掉同步代码块后看运行效果* 2.1 异常情况* Exception in thread "t1" java.lang.ILLegalMonitorStateException at java.Lang.Object.wait(Native Method)* Exception in thread "t2" java.Lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method)* 2.2 结论* Object类中的wait、notify、notifyALL用于线程等待和唤醒的方法,都必须在synchronized内部执行(必须用到关键字synchronized)。*/
public class LockSupportDemo_2 {static Object objectLock =new Object();public static void main(String[] args) {new Thread(()->{
// synchronized (objectLock){System.out.println(Thread.currentThread().getName()+"\t"+"------come in");try {objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t"+"------被唤醒");
// }},"A").start();new Thread(()->{
// synchronized (objectLock){objectLock.notify();System.out.println(Thread.currentThread().getName()+"\t"+"------通知");
// }},"B").start();}
}
wait方法和notify方法,两个都去掉同步代码块
- 异常情况
A ------come in
Exception in thread "A" Exception in thread "B" java.lang.IllegalMonitorStateExceptionat java.lang.Object.notify(Native Method)at juc.LockSupportDemo_2.lambda$main$1(LockSupportDemo_2.java:22)at java.lang.Thread.run(Thread.java:745)
java.lang.IllegalMonitorStateExceptionat java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:502)at juc.LockSupportDemo_2.lambda$main$0(LockSupportDemo_2.java:13)at java.lang.Thread.run(Thread.java:745)Process finished with exit code 0
- 异常2
package juc;import java.util.concurrent.TimeUnit;/*** 要求:t1线程等待3秒钟,3秒钟后t2线程唤龌t1线程继续工作** 3 将notify放在wait方法前先执行,t1先notify了,3秒钟后t2线程再执行ait方法* 3.1程序一直无法结桑1* 3.2结论* 先wait后notify、notifyalL方法,等待中的线程才会被唤醒,否则无法唤醒*/
public class LockSupportDemo_3 {static Object objectLock =new Object();public static void main(String[] args) {new Thread(()->{//暂停几秒钟线程try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }synchronized (objectLock){System.out.println(Thread.currentThread().getName()+"\t"+"------come in");try {objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t"+"------被唤醒");}},"A").start();new Thread(()->{synchronized (objectLock){objectLock.notify();System.out.println(Thread.currentThread().getName()+"\t"+"------通知");}},"B").start();}
}
将notify放在wait方法前面
程序无法热行,无法唤醒
小总结
wait和notify方法必须要在同步块或者方法里面且成对出现使用,否则会抛出java.lang.IllegalMonitorStateException。
调用顺序要先wait后notify才OK。
12_awaitSignal限制
Condition接口中的await后signal方法实现线程的等待和唤醒
Condition接口中的await后signal方法实现线程的等待和唤醒,与Object类中的wait和notify方法实现线程等待和唤醒类似。
代码
- 正常
package juc;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockSupportDemo_2_1 {static Object objectLock = new Object();static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t"+"-----被唤醒");} finally {lock.unlock();}}, "A").start();new Thread(() -> {lock.lock();try {condition.signal();System.out.println(Thread.currentThread().getName() + "\t" + "------通知");} finally {lock.unlock();}}, "B").start();}
}
- 异常1
package juc;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockSupportDemo_2_2 {static Object objectLock = new Object();static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {
// lock.lock();try {System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t"+"-----被唤醒");} finally {
// lock.unlock();}}, "A").start();new Thread(() -> {
// lock.lock();try {condition.signal();System.out.println(Thread.currentThread().getName() + "\t" + "------通知");} finally {
// lock.unlock();}}, "B").start();}
}
- 异常2
package juc;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockSupportDemo_2_3 {static Object objectLock = new Object();static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {//暂停几秒钟线程try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }lock.lock();try {System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t"+"-----被唤醒");} finally {lock.unlock();}}, "A").start();new Thread(() -> {lock.lock();try {condition.signal();System.out.println(Thread.currentThread().getName() + "\t" + "------通知");} finally {lock.unlock();}}, "B").start();}
}
小总结
await和signal方法必须要在同步块或者方法里面且成对出现使用,否则会抛出java.lang.IllegalMonitorStateException。
调用顺序要先await后signal才OK。
传统的synchronized和lLock实现等待唤醒通知的约束
- 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
- 必须要先等待后唤醒 ,线程才能够被唤醒
13_LockSupport方法介绍
LockSupport类中的park等待和unpark唤醒
是什么
- 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
- 官网解释
主要方法
-
API
-
阻塞
-
park() /park(Object blocker)
-
阻塞当前线程/阻塞传入的具体线程
-
-
唤醒
-
unpark(Thread thread)
-
唤醒处于阻塞状态的指定线程
-
14_LockSupport案例解析
代码
- 正常+无锁块要求
package juc;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;public class LockSupportDemo_3_1 {static Object objectLock = new Object();static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {Thread a = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");LockSupport.park();//被阻塞...等待通知等待放行,它要通过需要许可证System.out.println(Thread.currentThread().getName() + "\t" + "-----被唤醒");}, "a");a.start();//暂停几秒钟线程try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }Thread b =new Thread(() -> {LockSupport.unpark(a);System.out.println(Thread.currentThread().getName() + "\t" + "------通知");}, "b");b.start();}
}
- 之前错误的先唤醒后等待,LockSupport照样支持
package juc;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;public class LockSupportDemo_3_2 {static Object objectLock = new Object();static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {Thread a = new Thread(() -> {//暂停几秒钟线程try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }System.out.println(Thread.currentThread().getName() + "\t" + "-----come in"+System.currentTimeMillis());LockSupport.park();//被阻塞...等待通知等待放行,它要通过需要许可证 //1675241932025--没起作用--1675241932025System.out.println(Thread.currentThread().getName() + "\t" + "-----被唤醒"+System.currentTimeMillis());}, "a");a.start();Thread b =new Thread(() -> {LockSupport.unpark(a);System.out.println(Thread.currentThread().getName() + "\t" + "------通知");}, "b");b.start();}
}
- 解释
sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下先执行了unpark(t1)导致上面的park方法形同虚设无效,时间一样
重点说明(重要)
LockSupport是用来创建锁和共他同步类的基本线程阻塞原语。
LockSuport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻寨之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,
调用一次unpark就加1变成1,
调用一次park会消费permit,也就是将1变成0,同时park立即返回。
如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。
形象的理解
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
当调用park方法时
-
如果有凭证,则会直接消耗掉这个凭证然后正常退出。
-
如果无凭证,就必须阻塞等待凭证可用。
而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无放。
面试题
为什么可以先唤醒线程后阻塞线程?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1(不能累加),连续调用两次 unpark和调用一次 unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。
package juc;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;public class LockSupportDemo_3_3 {static Object objectLock = new Object();static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {Thread a = new Thread(() -> {//暂停几秒钟线程try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }System.out.println(Thread.currentThread().getName() + "\t" + "-----come in"+System.currentTimeMillis());LockSupport.park();//被阻塞...等待通知等待放行,它要通过需要许可证 //1675241932025--没起作用--1675241932025LockSupport.park();//被阻塞...等待通知等待放行,它要通过需要许可证 //1675241932025--没起作用--1675241932025System.out.println(Thread.currentThread().getName() + "\t" + "-----被唤醒"+System.currentTimeMillis());}, "a");a.start();Thread b =new Thread(() -> {LockSupport.unpark(a);LockSupport.unpark(a);//最多一个凭证System.out.println(Thread.currentThread().getName() + "\t" + "------通知");}, "b");b.start();}
}
15_AQS理论初步
4.AbstractQueuedSynchronizer之AQS
先从字节跳动及其它大厂面试题说起前置知识
- 同学反馈2020.6.27
【Java集合类】
1、从集合开始吧,介绍一下常用的集合类,哪些是有序的,哪些是无序的
2、hashmap是如何寻址的,哈希碰撞后是如何存储数据的,1.8后什么时候变成红黑树、说下红黑树的理解,红黑树有什么好处
3、concurrrenthashmap 怎么实现线程安全,一个里面会有几个段 segment,jdk1.8后有优化concurrenthashmap吗?分段锁有什么坏处
【多线程JUC】
4、reentrantlock实现原理,简单说下aqs
5、synchronized实现原理,monitor对象什么时候生成的?知道monitor的monitorenter和moni这两个是怎么保证同步的吗,或者说,这两个操作讦算机底层是如何执行的
6、刚刚你提到了synchronized的优化过程,详细说一下吧。偏向锁和轻量级锁有什么区别?
7、线程池几个参数说下,你们项目中如何根据实际场景设詈参数的,为什么cpu密集设置的线程密集型少
前置知识
- 公平锁和非公平锁
- 可重入锁
- LockSupport
- 自旋锁
- 数据结构之链表
- 设计模式之模板设计模式
是什么
-
字面意思
- 抽象的队列同步器
- 源代码
-
技术解释
是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态。
CLH:Craig、Landin and Hagersten队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO。
16_AQS能干嘛
AQS为什么是JUC内容中最重要的基石
和AQS有关的
-
ReentrantLock
-
CountDownLatch
-
ReentrantReadWriteLock
-
Semaphore
-
…
进一步理解锁和同步器的关系
-
锁,面向锁的 使用者 ----定义了程序员和锁交互的使用层APl,隐藏了实现细节,你调用即可
-
同步器,面向锁的 实现者 -----比如Java并发大神DougLee,提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。
能干嘛
-
加锁会导致阻塞
- 有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
-
解释说明
抢到资源的线程直接使用处理业务逻辑,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node),通过CAS、自旋以及LockSupport.park() 的方式,维护state变量的状态,使并发达到同步的控制效果。
17_AQS源码体系-上
AQS初步
AQS初识
-
官网解释
-
有阻塞就需要排队,实现排队必然需要队列
- AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFo队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node,节点来实现锁的分配,通过CAS完成对State值的修改。
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {private static final long serialVersionUID = 7373984972572414691L;* Creates a new {@code AbstractQueuedSynchronizer} instanceprotected AbstractQueuedSynchronizer() { }* Wait queue node class.static final class Node {* Head of the wait queue, lazily initialized. Except forprivate transient volatile Node head;* Tail of the wait queue, lazily initialized. Modified only viaprivate transient volatile Node tail;* The synchronization state.private volatile int state;* Returns the current value of synchronization state.protected final int getState() {* Sets the value of synchronization state.protected final void setState(int newState) {* Atomically sets synchronization state to the given updatedprotected final boolean compareAndSetState(int expect, int update) {...
}
18_AQS源码体系-下
AQS内部体系架构
AQS自身
-
AQS的int变量
-
AQS的同步状态state成员变量
-
state成员变量相当于银行办理业务的受理窗口状态。
-
零就是没人,自由状态可以办理
-
大于等于1,有人占用窗口,等着去
-
-
-
AQS的CLH队列
-
CLH队列(三个大牛的名字组成),为一个双向队列
-
银行候客区的等待顾客
-
-
小总结
- 有阻塞就需要排队,实现排队必然需要队列
- state变量+CLH变种的双端队列
AbstractQueuedSynchronizer内部类
-
Node的int变量
- Node的等待状态waitState成员变量
- 说人话
- 等候区其它顾客(其它线程)的等待状态队列中每个排队的个体就是一个Node
- Node的等待状态waitState成员变量
-
Node此类的讲解
- 内部结构
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...* Creates a new {@code AbstractQueuedSynchronizer} instanceprotected AbstractQueuedSynchronizer() { }* Wait queue node class.static final class Node {//表示线程以共享的模式等待锁/** Marker to indicate a node is waiting in shared mode */static final Node SHARED = new Node();//表示线程正在以独占的方式等待锁/** Marker to indicate a node is waiting in exclusive mode */static final Node EXCLUSIVE = null;//线程被取消了/** waitStatus value to indicate thread has cancelled */static final int CANCELLED = 1;//后继线程需要唤醒/** waitStatus value to indicate successor's thread needs unparking */static final int SIGNAL = -1;//等待condition唤醒/** waitStatus value to indicate thread is waiting on condition */static final int CONDITION = -2;//共享式同步状态获取将会无条件地传播下去* waitStatus value to indicate the next acquireShared should static final int PROPAGATE = -3;//当前节点在队列中的状态(重点)//说人话://等候区其它顾客(其它线程)的等待状态//队列中每个排队的个体就是一个Node//初始为0,状态上面的几种* Status field, taking on only the values:volatile int waitStatus;//前驱节点(重点)* Link to predecessor node that current node/thread relies onvolatile Node prev;//后继节点(重点)* Link to the successor node that the current node/threadvolatile Node next;//表示处于该节点的线程* The thread that enqueued this node. Initialized onvolatile Thread thread;//指向下一个处于CONDITION状态的节点* Link to next node waiting on condition, or the specialNode nextWaiter;* Returns true if node is waiting in shared mode.final boolean isShared() {//返回前驱节点,没有的话抛出npe* Returns previous node, or throws NullPointerException if null.final Node predecessor() throws NullPointerException {Node() { // Used to establish initial head or SHARED markerNode(Thread thread, Node mode) { // Used by addWaiterNode(Thread thread, int waitStatus) { // Used by Condition}...
}
- 属性说明
AQS同步队列的基本结构
19_AQS源码深度解读01
从我们的ReentrantLock开始解读AQS
Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的
public class ReentrantLock implements Lock, java.io.Serializable {private static final long serialVersionUID = 7373984872572414699L;/** Synchronizer providing all implementation mechanics */private final Sync sync;/*** Base of synchronization control for this lock. Subclassed* into fair and nonfair versions below. Uses AQS state to* represent the number of holds on the lock.*/abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;}public void lock() {sync.lock();}public void unlock() {sync.release(1);}
ReentrantLock的原理
从最简单的lock方法开始看看公平和非公平
/*** Creates an instance of {@code ReentrantLock}.* This is equivalent to using {@code ReentrantLock(false)}.*/public ReentrantLock() {sync = new NonfairSync();}/*** Creates an instance of {@code ReentrantLock} with the* given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()
hasQueuedPredecessors是公平锁加锁时判断等待队列中是否存在有效节点的方法
//公平锁加锁时判断等待队列中是否存在有效节点的方法public final boolean hasQueuedPredecessors() {// The correctness of this depends on head being initialized// before tail and on head.next being accurate if the current// thread is first in queue.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());// h != t 等待队列非空// (s = h.next) == null 队首是否为空// s.thread != Thread.currentThread()) 队首是否是当前线程// 判断等待队列是否是空或等待队列队首是否是当前线程}
非公平锁走起,方法lock()
对比公平锁和非公平锁的tyAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断!hasQueuedPredecessors()
hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:
公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;
非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程在unpark(),之后还是需要竞争锁(存在线程竞争的情况下)
- 本次讲解我们走最常用的,lock/unlock作为案例突破口
- 源码解读比较困难,别着急—阳哥的全系列脑图给大家做好笔记
- AQS源码深度分析走起
整个 ReentrantLock的加锁过程,可以分为三个阶段:
1、尝试加锁;
2、加锁失败,线程入队列;
3、线程入队列后,进入阻塞状态。
对应下面①②③三部分。
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
20_AQS源码深度解读02
ReentrantLock的示例程序
带入一个银行办理业务的案例来模拟我们的AQS 如何进行线程的管理和通知唤醒机制,3个线程模拟3个来银行网点,受理窗口办理业务的顾客。
package juc;import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;public class AQSDemo {public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();//带入一个银行办理业务的案例来模拟我们的AQs 如何进行线程的管理和通知唤醒机制\//3个线程模拟3个来银行网点,受理窗口办理业务的顾客//A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理new Thread(()->{lock.lock();try {System.out.println(Thread.currentThread().getName() + " come in.");try {TimeUnit.SECONDS.sleep(5);//模拟办理业务时间} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();}}, "Thread A").start();//第2个顾客,第2个线程---->,由于受理业务的窗口只有一个(只能一个线程持有锁),此代B只能等待,//进入候客区new Thread(()->{lock.lock();try {System.out.println(Thread.currentThread().getName() + " come in.");} finally {lock.unlock();}}, "Thread B").start();//第3个顾客,第3个线程---->,由于受理业务的窗口只有一个(只能一个线程持有锁),此代C只能等待,//进入候客区new Thread(()->{lock.lock();try {System.out.println(Thread.currentThread().getName() + " come in.");} finally {lock.unlock();}}, "Thread C").start();}
}
程序初始状态方便理解图
-
lock()
-
acquire()
-
tryAcquire(arg)
-
addWaiter(Node.EXCLUSIVE)
-
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
启动程序,首先是运行线程A,ReentrantLock默认是选用非公平锁。
public class ReentrantLock implements Lock, java.io.Serializable {...* Acquires the lock.public void lock() {sync.lock();//<------------------------注意,我们从这里入手,一开始将线程A的}abstract static class Sync extends AbstractQueuedSynchronizer {...//被NonfairSync的tryAcquire()调用final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}...}//非公平锁static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock. Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {//<----线程A的lock.lock()调用该方法if (compareAndSetState(0, 1))//AbstractQueuedSynchronizer的方法,刚开始这方法返回truesetExclusiveOwnerThread(Thread.currentThread());//设置独占的所有者线程,显然一开始是线程Aelseacquire(1);//稍后紧接着的线程B将会调用该方法。}//acquire()将会间接调用该方法protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);//调用父类Sync的nonfairTryAcquire()}}...
}
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {/*** The synchronization state.*/private volatile int state;//线程A将state设为1,下图红色椭圆区/*Atomically sets synchronization state to the given updated value if the current state value equals the expected value.This operation has memory semantics of a volatile read and write.*/protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);}}
线程A开始办业务了。
B将会等候
acquire(1);//稍后紧接着的线程B将会调用该方法。
调用acquire方法
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
21_AQS源码深度解读03
轮到线程B运行
public class ReentrantLock implements Lock, java.io.Serializable {...* Acquires the lock.public void lock() {sync.lock();//<------------------------注意,我们从这里入手,线程B的执行这}//非公平锁static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock. Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {//<-------------------------线程B的lock.lock()调用该方法if (compareAndSetState(0, 1))//这是预定线程A还在工作,这里返回falsesetExclusiveOwnerThread(Thread.currentThread());//elseacquire(1);//线程B将会调用该方法,该方法在AbstractQueuedSynchronizer,//它会调用本类的tryAcquire()方法}//acquire()将会间接调用该方法protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);//调用父类Sync的nonfairTryAcquire()}}//非公平锁与公平锁的公共父类* Base of synchronization control for this lock. Subclassedabstract static class Sync extends AbstractQueuedSynchronizer {//acquire()将会间接调用该方法 但是返回值会取反...final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();//这里是线程Bint c = getState();//线程A还在工作,c=>1//这种情况就是持有锁的线程A刚刚释放锁了,等待线程B就可以占用锁了if (c == 0) {//falseif (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current);return true;}}//这种情况就是持有锁的线程,还想占有锁 可重入锁else if (current == getExclusiveOwnerThread()) {//(线程B == 线程A) => false int nextc = c + acquires;//+1 if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;//最终返回false} ...}...
}
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...* Acquires in exclusive mode, ignoring interrupts. Implementedpublic final void acquire(int arg) {if (!tryAcquire(arg) &&//线程B调用非公平锁的tryAcquire(), 最终返回false,加上!,也就是true,也就是还要执行下面两行语句acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//下一节论述 Node.EXCLUSIVE排他的selfInterrupt();}...
}
另外
假设线程B,C还没启动,正在工作线程A重新尝试获得锁,也就是调用lock.lock()多一次
//非公平锁与公平锁的公共父类fa* Base of synchronization control for this lock. Subclassedabstract static class Sync extends AbstractQueuedSynchronizer {...final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();//这里是线程Aint c = getState();//线程A还在工作,c=>1;如果线程A恰好运行到在这工作完了,c=>0,这时它又要申请锁的话if (c == 0) {//线程A正在工作为false;如果线程A恰好工作完,c=>0,这时它又要申请锁的话,则为trueif (compareAndSetState(0, acquires)) {//线程A重新获得锁setExclusiveOwnerThread(current);//这里相当于NonfairSync.lock[添加链接描述](https://www.bilibili.com/video/BV1Hy4y1B78T?p=23)()另一重设置吧!return true;}}else if (current == getExclusiveOwnerThread()) {//(线程A == 线程A) => trueint nextc = c + acquires;//1+1=>nextc=2if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);//state=2,说明要unlock多两次吧(现在盲猜)return true;//返回true}return false;} ...}
22_AQS源码深度解读04
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...* Acquires in exclusive mode, ignoring interrupts. Implementedpublic final void acquire(int arg) {if (!tryAcquire(arg) &&//线程B调用非公平锁的tryAcquire(), 最终返回false,加上!,也就是true,也就是还要执行下面两行语句acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//线程B加入等待队列selfInterrupt();//下一节论述}private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode); //此时是线程B 模式是排他 此时等候队列无结点// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {//根据上面一句注释,本语句块的意义是将新节点快速添加至队尾node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);//快速添加至队尾失败,则用这方法调用(可能链表为空,才调用该方法)return node;}//Inserts node into queue, initializing if necessary.private Node enq(final Node node) {for (;;) { Node t = tail;//t=上一次循环的尾指针也就是虚拟头结点if (t == null) { // Must initialize 此时尾指针为空if (compareAndSetHead(new Node()))//插入一个哨兵节点(或称傀儡节点) 头指针-->虚拟头结点tail = head; //尾指针-->虚拟头结点} else { //循环第二次走以下代码node.prev = t; //虚拟头结点t<---Bif (compareAndSetTail(t, node)) {//真正插入我们需要的节点,也就是包含线程B引用的节点 尾指针指向tail--->B结点t.next = node; //虚拟头结点t--->Breturn t;}}}}//CAS head field. Used only by enq.private final boolean compareAndSetHead(Node update) {return unsafe.compareAndSwapObject(this, headOffset, null, update);}//CAS tail field. Used only by enq.private final boolean compareAndSetTail(Node expect, Node update) {return unsafe.compareAndSwapObject(this, tailOffset, expect, update);}...
}
线程B加入等待队列。
23_AQS源码深度解读05
线程A依然工作,线程C如线程B那样炮制加入等待队列。
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...* Acquires in exclusive mode, ignoring interrupts. Implementedpublic final void acquire(int arg) {if (!tryAcquire(arg) &&//线程C调用非公平锁的tryAcquire(), 最终返回false,加上!,也就是true,也就是还要执行下面两行语句acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//线程C加入等待队列selfInterrupt();//下一节论述}private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode); //此时是线程C 模式是排他 此时等候队列有虚拟头结点 结点B// Try the fast path of enq; backup to full enq on failureNode pred = tail;//此时前驱结点pred=尾结点tail=B//将新节点快速添加至队尾if (pred != null) {//B!=nullnode.prev = pred; //B<---Cif (compareAndSetTail(pred, node)) {//尾指针tail--->Cpred.next = node;//前驱结点B--->Creturn node;//返回}}enq(node);//快速添加至队尾失败,则用这方法调用(可能链表为空,才调用该方法)return node;}...
}
-
acquire() ----源码和三大流程
-
tryAcquire(arg)-----本次走非公平锁
-----nonfairTryAcquire(acquires) -
true-----继续推进条件,走下一个方法addWaiter
-
false-----结束
-
addWaiter(Node.EXCLUSIVE)
- addwaiter (Node mode)
- enq(node);
- 双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。
- 假如3号Threadc线程进来
- prev
- compareAndSetTail
- next
- addwaiter (Node mode)
24_AQS源码深度解读06
-
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
- acquireQueued
- 假如再抢抢失败就会进入
- acquireQueued
-
shouldParkAfterFailedAcquire和 parkAndChecklnterrupt方法中
-
shouldParkAfterFailedAcquire
- 如果前驱节点的 waitStatus是SIGNAL状态,即 shouldParkAfterFailedAcquire方法会返回true程序会继续向下执行parkAndCheckInterrupt方法,用于将当前线程挂起
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//获取前驱结点的状态int ws = pred.waitStatus;//如果是SIGNAL状态,即等待被占用的资源释放,直接返回 true//准备继续调用 parkAndCheckInterrupt方法if (ws == Node.SIGNAL)return true;//ws大于0说明是CANCELLED状态,if (ws > 0) {//跳过//循环判断前驱节点的前驱节点是否也为CANCELLED状态,忽略该状态的节点,重新连接队列do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {//将当前节点的前驱节点设置为设置为SIGNAL状态,用于后续唤醒操作//程序第一次执行到这返回为false,还会进行外层第二次循环,最终从代码第7行返回compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}
- parkAndCheckInterrupt
程序测试
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...* Acquires in exclusive mode, ignoring interrupts. Implementedpublic final void acquire(int arg) {if (!tryAcquire(arg) &&//线程B调用非公平锁的tryAcquire(), 最终返回false,加上!,也就是true,也就是还要执行下面两行语句//线程B加入等待队列,acquireQueued本节论述<--------------------------acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();//}//Acquires in exclusive uninterruptible mode for thread already inqueue. //Used by condition wait methods as well as acquire.////return true if interrupted while waitingfinal boolean acquireQueued(final Node node, int arg) {boolean failed = true;//是否被取消try {boolean interrupted = false//是否被打断for (;;) {final Node p = node.predecessor();//1.返回前驱节点,对与线程B来说,p也就是傀儡节点 对线程C的前驱结点是B//p==head为true,tryAcquire()方法说明请转至 #21_AQS源码深度解读03//假设线程A正在工作,现在线程B只能等待,所以tryAcquire(arg)返回false,下面的if语块不执行////第二次循环,假设线程A继续正在工作,下面的if语块还是不执行 对于C线程 进入不了if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}//请移步到2.处的shouldParkAfterFailedAcquire()解说。第一次返回false, 下一次(第二次)循环//第二次循环,shouldParkAfterFailedAcquire()返回true,执行parkAndCheckInterrupt()if (shouldParkAfterFailedAcquire(p, node) && //4. parkAndCheckInterrupt())//park导致BC阻塞到这方法里了interrupted = true;//原来一直想tryAcquire,现在正在阻塞了,在候客区了}} finally {if (failed)cancelAcquire(node);//取消排队}}static final class Node {...//1.返回前一节点final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}...}//2. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;//此时pred指向傀儡节点,它的waitStatus为0//Node.SIGNAL为-1,跳过//第二次调用,ws为-1,条件成立,返回trueif (ws == Node.SIGNAL)//-1/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;if (ws > 0) {//跳过/** Predecessor was cancelled. Skip over predecessors and* indicate retry.*/do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {/** waitStatus must be 0 or PROPAGATE. Indicate that we* need a signal, but don't park yet. Caller will need to* retry to make sure it cannot acquire before parking.*///3. 傀儡节点的WaitStatus设置为-1//下图红圈compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;//第一次返回}/*** CAS waitStatus field of a node.*///3.private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);}/*** Convenience method to park and then check if interrupted** @return {@code true} if interrupted*///4.private final boolean parkAndCheckInterrupt() {//前段章节讲述的LockSupport,this指的是NonfairSync对象,//这意味着真正阻塞线程B,同样地阻塞了线程CLockSupport.park(this);//线程B,C在此处暂停了运行<-------------------------return Thread.interrupted();}}
图中的B结点把傀儡节点的waitStatus由0变为-1(Node.SIGNAL)。
C结点会把B结点的waitStatus由0变为-1(Node.SIGNAL)。
25_AQS源码深度解读07
接下来讨论ReentrantLock.unLock()方法。假设线程A工作结束,调用unLock(),释放锁占用。
方法unlock()
- sync.release(1);
- tryRelease(int arg);
- unparkSuccessor
- 杀回马枪
- unparkSuccessor
- tryRelease(int arg);
public class ReentrantLock implements Lock, java.io.Serializable {private final Sync sync;abstract static class Sync extends AbstractQueuedSynchronizer {...//2.unlock()间接调用本方法,releases传入1protected final boolean tryRelease(int releases) {//3.int c = getState() - releases;//c为0//4.if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {//c为0,条件为ture,执行if语句块free = true;//5.setExclusiveOwnerThread(null);}//6.setState(c);//设置状态为0return free;//最后返回true}...}static final class NonfairSync extends Sync {...}public ReentrantLock() {sync = new NonfairSync();//我们使用的非公平锁}//注意!注意!注意!public void unlock() {//<----------从这开始,假设线程A工作结束,调用unLock(),释放锁占用//1.sync.release(1);//在AbstractQueuedSynchronizer类定义}...}
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...//1.public final boolean release(int arg) {//2.if (tryRelease(arg)) {//该方法看子类NonfairSync实现,最后返回trueNode h = head;//返回傀儡节点if (h != null && h.waitStatus != 0)//傀儡节点非空,且状态为-1,条件为true,执行if语句//7.unparkSuccessor(h);return true;}return false;//返回true,false都无所谓了,unlock方法只是简单调用release方法,对返回结果没要求}/*** The synchronization state.*/private volatile int state;//3.protected final int getState() {return state;}//6.protected final void setState(int newState) {state = newState;}//7. Wakes up node's successor, if one exists.//传入傀儡节点private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling. It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;//傀儡节点waitStatus为-1if (ws < 0)//ws为-1,条件成立,执行if语块compareAndSetWaitStatus(node, ws, 0);//8.将傀儡节点waitStatus由-1变为0/** Thread to unpark is held in successor, which is normally* just the next node. But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/Node s = node.next;//傀儡节点的下一节点,也就是带有线程B的节点if (s == null || s.waitStatus > 0) {//s非空,s.waitStatus非0,条件为false,不执行if语块s = null;for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)//s非空,条件为true,执行if语块LockSupport.unpark(s.thread);//唤醒线程B。运行到这里,线程A的工作基本告一段落了。}//8.private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);}}
public abstract class AbstractOwnableSynchronizerimplements java.io.Serializable {...protected AbstractOwnableSynchronizer() { }private transient Thread exclusiveOwnerThread;//5.protected final void setExclusiveOwnerThread(Thread thread) {exclusiveOwnerThread = thread;}//4.protected final Thread getExclusiveOwnerThread() {return exclusiveOwnerThread;}
}
线程A结束工作,调用unlock()的tryRelease()后的状态,state由1变为0,exclusiveOwnerThread由线程A变为null。
线程B被唤醒,即从原先park()的方法继续运行
public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {private final boolean parkAndCheckInterrupt() {LockSupport.park(this);//线程B从阻塞到非阻塞,继续执行return Thread.interrupted();//线程B没有被中断,返回false}...//Acquires in exclusive uninterruptible mode for thread already inqueue. //Used by condition wait methods as well as acquire.////return true if interrupted while waitingfinal boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();//线程B所在的节点的前一节点是傀儡节点//傀儡节点是头节点,tryAcquire()的说明请移步至#21_AQS源码深度解读03//tryAcquire()返回true,线程B成功上位if (p == head && tryAcquire(arg)) {setHead(node);//1.将附带线程B的节点的变成新的傀儡节点p.next = null; // help GC//置空原傀儡指针与新的傀儡节点之间的前后驱指针,方便GC回收failed = false;return interrupted;//返回false,跳到2.acquire()}if (shouldParkAfterFailedAcquire(p, node) && //唤醒线程B继续工作,parkAndCheckInterrupt()返回false//if语块不执行,跳到下一循环parkAndCheckInterrupt())//<---------------------------------唤醒线程在这里继续运行interrupted = true;}} finally {if (failed)cancelAcquire(node);}}//1. private void setHead(Node node) {head = node;node.thread = null;node.prev = null;}//2.* Acquires in exclusive mode, ignoring interrupts. Implementedpublic final void acquire(int arg) {if (!tryAcquire(arg) &&//acquireQueued()返回fasle,条件为false,if语块不执行,acquire()返回//也就是说,线程B成功获得锁,可以展开线程B自己的工作了。acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();//}}
最后,线程B上位成功。
26_AQS小总结
aqs脑图
最后
2023-2-2 17:12:40
这篇博客能写好的原因是:站在巨人的肩膀上
这篇博客要写好的目的是:做别人的肩膀
开源:为爱发电
学习:为我而行
相关文章:

3.JUC【Java面试第三季】
3.JUC【Java面试第三季】前言推荐3.JUC06_闲聊AQS面试1.题目说明07_可重入锁理论2.可重入锁说明“可重入锁”这四个字分开来解释可重入锁的种类08_可重入锁的代码验证-上09_可重入锁的代码验证-下3.LockSupport10_LockSupport是什么LockSupport是什么11_waitNotify限制线程等待…...

Linux防火墙(7)
实验目的 通过该实验了解Linux防火墙iptables实现原理,掌握iptables基本使用方法,能够利用iptables对操作系统进行加固。预备知识基本原理 netfilter/iptables(简称为iptables)组成Linux平台下的包过滤防火墙,具有完成…...
2.11整理(2)(主要关于teacher forcing)
teacher forcing 训练迭代过程早期的RNN预测能力非常弱,几乎不能给出好的生成结果。如果某一个unit产生了垃圾结果,必然会影响后面一片unit的学习。RNN存在着两种训练模式(mode): free-running mode:就是常见的那种训练网络的方式: 上一个sta…...

亿级高并发电商项目-- 实战篇 --万达商城项目 三(通用模块、商品服务模块、后台API模块、IDEA忽略文件显示等开发工作
专栏:高并发项目 👏作者简介:大家好,我是小童,Java开发工程师,CSDN博客博主,Java领域新星创作者 📕系列专栏:前端、Java、Java中间件大全、微信小程序、微信支付、若依框…...

IDEA下java程序的调试(简易实例图示版)
在线排版不太好看,介意的读者可下载word下来看:https://download.csdn.net/download/xijinno1/87441301IDEA下java程序的简单调试-System.out.println首先本次进行调试的一个程序是实现从1累加到100的功能,是在IDEA下进行编写的。如图所示&am…...

动态规划算法
1.应用场景-背包问题 背包问题:有一个背包,容量为 4 磅 , 现有如下物品 要求达到的目标为装入的背包的总价值最大,并且重量不超出要求装入的物品不能重复 2.动态规划算法介绍 动态规划(Dynamic Programming)算法的核心思想是&…...

nacos的单机模式和集群模式
文章目录 目录 文章目录 前言 一、nacos数据库配置 二、单机模式 三、集群模式 四、使用nginx集群模式的负载均衡 总结 前言 一、nacos数据库配置 在数据库中创建nacos_config 编码格式utf8-mb4的数据库 把上面的数据库文件导入数据库 在 配置文件中添加如下 spring.datasour…...

Spring Boot 整合定时任务完成 从0 到1
Java 定时任务学习 定时任务概述 > 定时任务的应用场景非常广泛, 如果说 我们想要在某时某地去尝试的做某件事 就需要用到定时任务来通知我们 ,大家可以看下面例子 如果需要明天 早起,哪我们一般会去定一个闹钟去通知我们, 而在编程中 有许许多多的…...
Dialogue Transformers
Abstract 本文介绍了一种基于 Transformer 架构的 对话策略,其中自注意力机制被应用于对话轮次(dialogue turns)的序列上。近期的一些工作使用层次化的循环神经网络(hierarchical recurrent neural networks)在对话上下文中对多个话语(utterances)进行编码,但是我们认…...

【遇见青山】项目难点:缓存击穿问题解决方案
【遇见青山】项目难点:缓存击穿问题解决方案1.缓存击穿互斥锁🔒方案逻辑过期方案2.基于互斥锁方案的具体实现3.基于逻辑过期方案的具体实现1.缓存击穿 缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效…...
2023Flag具体实施计划(短期)
重新看了flag ,要做的事情太多,太杂,上周一周时间都在纠结和琢磨,该怎么下手。如何达成小目标。特别是沟通,汇报,演讲能力, 以及整体体系化的思维能力的训练。如何做到多思考,而不是瞎搞。这边重…...

研一寒假C++复习笔记--左值和右值的理解和使用
目录 1--左值和右值的定义 2--简单理解左值和右值的代码 3--非const引用只能接受左值 1--左值和右值的定义 左值:L-Value,L理解为 Location,表示可寻; 右值:R-Value,R理解为 Read,表示可读&a…...
Android 11.0 动态修改SystemProperties中ro开头系统属性的值
需求: 在11.0的产品开发中,对于定制功能的需求很多,有些机型要求可以修改系统属性值,对于系统本身在10.0以后为了系统安全性,不允许修改ro开头的SystemProperties的值,所以如果要求修改ro的相关系统属性&am…...

为什么分库分表
系列文章目录 文章目录系列文章目录前言一、什么是分库分表二、分库分表的原因分库分表三、如何分库分表3.1 垂直拆分1.垂直分库2、垂直分表3.2 水平拆分水平分库水平分表水平分库分表的策略hash取模算法range范围rangehash取模混合地理位置分片预定义算法四、分库分表的问题分…...

1625_MIT 6.828 stabs文档信息整理_下
全部学习汇总: GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 继续之前的学习笔记,整理一下最近看过的一点stabs资料。 这一页中有一半的信息是Fortran专用的,直接跳过。参数的符号修饰符是p,…...

论文阅读 | Rethinking Coarse-to-Fine Approach in Single Image Deblurring
前言:ICCV2021图像单帧运动去糊论文 论文地址:【here】 代码地址:【here】 Rethinking Coarse-to-Fine Approach in Single Image Deblurring 引言 图像去糊来自与物体或相机的运动。现有的deblur领域的深度学习方法大多都是coarse-to-fin…...

Mysql 增删改查(二)—— 增(insert)、删(delete)、改(update)
目录 一、插入 1、insert 2、replace(插入否则更新) 二、更新(update) 三、删除 1、delete 2、truncate(截断表,慎用) 一、插入 1、insert (1) 单行 / 多行插入 全列插入:…...
JSD2212复习串讲
1. Java语言基础阶段 这一部分主要是练,给一些题目还有讲解一些最基础的语法,做一些额外的补充 1.1 基本概念 1.2 变量 1.2.1 数据类型 4类8种 基本类型:整形、浮点型、字符型、布尔型 整形:byte -》short-》int-》long 浮点…...
sphinx 升级到6.x后的Jquery问题
sphinx 升级到6.0 后,以前对于jquery的默认引用方式发生了改变以前在编译后的html中jquery是如下引用的:<script src"_static/jquery.js"></script>而升级到6.0后,对于jquery 是一个googleapi的远程jquery调用…...

NSSCTF Round#8 Basic
from:http://v2ish1yan.top MyDoor 使用php伪协议读取index.php的代码 php://filter/readconvert.base64-encode/resourceindex.php<?php error_reporting(0);if (isset($_GET[N_S.S])) {eval($_GET[N_S.S]); }if(!isset($_GET[file])) {header(Location:/index.php?fi…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...