JUC并发编程
2024-08-07 09:48:14 2 举报
AI智能生成
登录查看完整内容
111
作者其他创作
大纲/内容
资源类
操作
线程
高内聚低耦合前提下,封装思想
判断、干活、通知
注意标志位flag,可能是volatile的
零基础就业班,阳哥JUC四大口诀
ReentrantReadWriteLock
Condition
ReentrantLock
CountDownLatch
CyclicBarrier
Semaphore
工具类
线程池与阻塞队列
ForkJoinPool与ForkJoinTask
Java8新特性函数式编程、方法引用、lambda Express
原子操作类Atomic
volatile
Callable和FutureTask
。。。。。。
JUC要求的知识内容,请回忆下
零基础就业班JUC知识串讲复习,快快过一下
本课程学生对象
易中难章节说明
阿里P6---P7
技术栈
阿里手册规范
阿里P6、P7对高级Java开发工程师的要求明细
本课程的难度对标
本课程前置要求说明
Doug Lea(道格.利)
先拜拜大神
零基础就业班讲解时阳哥JUC四大口诀
摩尔定律失效
硬件方面
zhuanability
高并发系统,异步+回调等生产需求
软件方面
为什么多线程极其重要???
private native void start0();
Java语言本身底层就是C++语言
http://openjdk.java.net/
openjdk8\\hotspot\\src\\share\\vm\untime
建议下载源码到本地观看
OpenJDK源码网址
Java线程理解以及openjdk中的实现
thread.c
openjdk8\\jdk\\src\\share\ative\\java\\lang
jvm.cpp
openjdk8\\hotspot\\src\\share\\vm\\prims
thread.cpp
更加底层的C++源码解读
从start一个线程说起
是程序的⼀次执⾏,是系统进⾏资源分配和调度的独⽴单位,每⼀个进程都有它⾃⼰的内存空间和系统资源
进程
在同⼀个进程内⼜可以执⾏多个任务,⽽这每⼀个任务我们就可以看做是⼀个线程
⼀个进程会有1个或多个线程的
分支主题
面试题:何为进程和线程?
Monitor(监视器),也就是我们平时所说的锁
JVM第3版
管程
Java多线程相关概念
Java线程分为用户线程和守护线程,线程的daemon属性为true表示是守护线程,false表示是用户线程
是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程
守护线程
是系统的工作线程,它会完成这个程序需要完成的业务操作
用户线程
默认为用户线程
code
如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以当系统只剩下守护进程的时候,java虚拟机会自动退出
当程序中所有用户线程执行完毕之后,不管守护线程是否结束,系统都会自动退出
why
注意:设置守护线程,需要在start()方法之前进行
重点
用户线程和守护线程
线程基础知识复习
Future和Callable接口
本源的Future接口相关架构
一旦调用get()方法,不管是否计算完成都会等待结果返回,导致阻塞,o(╥﹏╥)o
一般建议放在程序最后
get方法一旦调用会去取异步返回结果,如果此时异步处理还没有结束,需要等待,导致阻塞,后面代码无法执行
get()获取返回值会阻塞
Code
轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果.
isDone()轮询
Code2
过时不候
不见不散
轮询
小总结
应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知
将两个异步计算合成一个异步计算,这两个异步计算互相独立,同时第二个又依赖第一个的结果。
当Future集合中某个任务最快结束时,返回结果。
等待Future结合中的所有任务都完成。
想完成一些复杂的任务
从之前的FutureTask开始
类架构说明
是什么
接口CompletionStage
类CompletableFuture
CompletableFuture和CompletionStage源码分别介绍
public static CompletableFuture runAsync(Runnable runnable)
runAsync 无 返回值
public static CompletableFuture supplyAsync(Supplier supplier)
supplyAsync 有 返回值
没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool() 作为它的线程池执行异步代码。
如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码
上述Executor executor参数说明
get获取的值是null
无 返回值
get方法获取返回值
whenComplete
异常处理(e - {})
exceptionally最终处理方法,返回值或者抛出异常
函数式编程
有 返回值
从Java8开始引入了CompletableFuture,它是Future的功能增强版,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
解释下为什么默认线程池关闭,自定义线程池记得关闭
Code之通用演示,减少阻塞和轮询
异步任务结束时,会自动回调某个对象的方法;
异步任务出错时,会自动回调某个对象的方法;
主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行
主线程执行不受异步线程影响,主线程无需等待get获取结果而阻塞
CompletableFuture的优点
核心的四个静态方法,来创建一个异步操作
对Future的改进
大厂面试题看看
内部消化,无输入输出
Runnable
输入输出(一个参数)
Function
有输入无输出
BiConsumer
Consumer
提供者/生产者
无输入,有输出
Supplier
Lambda +Stream+链式调用+Java8函数式编程带走
函数式编程已经主流
先说说join和get对比
说说你过去工作中的项目亮点?
切记,功能→性能
对内微服务多系统调用
对外网站比价
大厂业务需求说明
导入Lombok依赖
一波流Java8函数式编程带走
案例精讲-从电商网站的比价需求说开去
一直死等到结果,容易阻塞
public T get()
必须在指定时间拿到结果,超过指定时间报timeout错误,容易出现阻塞
类似get
public T join()
当前线程需要数据时,如果没有计算线程没有完成计算的情况下,给我一个替代结果
计算完,返回计算完成后的结果
没算完,返回设定的valueIfAbsent值
立即获取结果不阻塞
public T getNow(T valueIfAbsent)
获取结果
判断多线程是否已经计算出结果,如果没有计算结果,返回true,且根据这个结果做后续定制
是否打断get方法立即返回自定义括号值
打断方法返回true,并返回设定值,正常执行则为false,返回线程计算值
public boolean complete(T value)
主动触发计算
获得结果和触发计算
code2
计算结果存在依赖关系,这两个线程串行化
由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停。
串行化处理,中途出现异常就会报错终止
thenApply
有异常也可以往下一步走,根据带的异常参数可以进一步处理
上一步的异常可以在下一步中被捕获和处理,本层无法处理,不进行处理,同样会中断异步执行结果,并且异常出现的数据为null无法使用
handle
总结
对计算结果进行中间处理
接收任务的处理结果,并消费处理,无返回结果
thenAccept
thenRun(Runnable runnable)
任务 A 执行完执行 B,并且 B 不需要 A 的结果
无输入无输出
各忙各的
thenRun
消费型函数接口
thenAccept(Consumer action)
任务 A 执行完执行 B,B 需要 A 的结果,但是任务 B 无返回值
有输入有输出
thenApply(Function fn)
任务 A 执行完执行 B,B 需要 A 的结果,同时任务 B 有返回值
Code之任务之间的顺序执行
以thenRun和thenRunAsync为例,有什么区别?
源码分析
CompletableFuture和线程池的说明
补充
对计算结果进行消费
谁快用谁
applyToEither
类似于比较equal来进行判断
对计算速度进行选用
两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine 来处理
先完成的先等着,等待其它分支任务
code标准版,好理解先拆分
自己理解+动手
code表达式
thenCombine
对计算结果进行合并
CompletableFuture常用方法
CompletableFuture
大厂面试题复盘
适合写操作多的场景,先加锁可以保证写操作时数据正确。
显式的锁定之后再操作同步资源
一句话:狼性锁
悲观锁
适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
乐观锁则直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再抢
采用版本号机制
CAS(Compare-and-Swap,即比较并替换)算法实现
乐观锁一般有两种实现方式:
乐观锁
伪代码说明
从轻松的乐观锁和悲观锁开讲
锁相关的8种案例演示
看看JVM中对应的锁在哪里?
承前启后的复习一下
JDK源码(notify方法)说明举例
作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
作用于代码块,对括号里配置的对象加锁。
作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
8种锁的案例实际体现在3个地方
synchronized有三种应用方式
-c 对代码进行反汇编
javap -v ***.class文件反编译
-v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
假如你需要更多信息
javap -c ***.class文件反编译
反编译
实现使用的是monitorenter和monitorexit指令
synchronized同步代码块
m1方法里面自己添加一个异常试试
一定是一个enter两个exit吗?
调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。如果设置了,执行线程会将先持有monitor然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor
synchronized普通同步方法
synchronized静态同步方法
从字节码角度分析synchronized实现
为什么任何一个对象都可以成为一个锁
大厂面试题讲解
在HotSpot虚拟机中,monitor采用ObjectMonitor实现
ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp
objectMonitor.hpp
每个对象天生都带着一个对象监视器
上述C++源码解读
什么是管程monitor
反编译synchronized锁的是什么
提前剧透,混个眼熟
对于synchronized关键字,我们在《Synchronized与锁升级》章节还会再深度讲解
通过8种情况演示锁运行案例,看看我们到底锁的是什么
从ReentrantLock卖票编码演示公平和非公平现象
源码解读
为什么会有公平锁/非公平锁的设计为什么默认非公平?
使⽤公平锁会有什么问题
什么时候用公平?什么时候用非公平?
面试题
何为公平锁/非公平锁?
更进一步的源码深度分析见后续第13章
预埋伏AQS
公平锁和非公平锁
说明
可:可以。
重:再次。
入:进入。
锁:同步锁。
进入同步域(即同步代码块/方法或显式锁锁定的代码)
进入什么
一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。
自己可以获取自己的内部锁
一句话
“可重入锁”这四个字分开来解释:
同步块
同步方法
隐式锁(即synchronized关键字使用的锁)默认是可重入锁
Synchronized的重入的实现机理
显式锁(即Lock)也有ReentrantLock这样的可重入锁。
可重入锁种类
可重入锁(又名递归锁)
系统资源不足
进程运行推进的顺序不合适
资源分配不当
产生死锁主要原因
请写一个死锁代码case
jps -l
jstack 进程编号
纯命令
jconsole
图形化
如何排查死锁
死锁及排查
源码深度分析见后续第14章
写锁(独占锁)/读锁(共享锁)
源码深度分析见后续第8章
自旋锁SpinLock
StampedLock
有没有比读写锁更快的锁?
无锁→独占锁→读写锁→邮戳锁
源码深度分析见后续第12章
无锁→偏向锁→轻量锁→重量锁
严禁这么做
不可以String同一把锁
其它细节
说说Java“锁”事
从阿里蚂蚁金服面试题讲起
什么是中断?
中断的三大方法相关API
修改状态
停止程序的运行
在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑。
通过一个volatile变量实现
通过AtomicBoolean
实例方法interrupt(),没有返回值
实例方法isInterrupted,返回布尔值
API
通过Thread类自带的中断api方法实现
中断运行中的线程方法
code02
结论
code02后手案例(重要,面试就是它,操蛋)
中断只是一种协同机制,修改中断标识位仅此而已,不是立刻stop打断
当前线程的中断标识为true,是不是就立刻停止?
静态方法Thread.interrupted()
都会返回中断状态,两者对比
面试题:如何使用中断标识停止线程?
线程中断机制
LockSupport是什么
方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
3种让线程等待和唤醒的方法
正常
wait方法和notify方法,两个都去掉同步代码块
异常情况
异常1
将notify放在wait方法前面
程序无法执行,无法唤醒
异常2
代码
wait和notify方法必须要在同步块或者方法里面,且成对出现使用
先wait后notify才OK
Object类中的wait和notify方法实现线程等待和唤醒
去掉lock/unlock
先signal后await
Condtion中的线程等待和唤醒方法之前,需要先获取锁
一定要先await后signal,不要反了
Condition接口中的await后signal方法实现线程的等待和唤醒
线程先要获得并持有锁,必须在锁块(synchronized或lock)中
必须要先等待后唤醒,线程才能够被唤醒
Object和Condition使用的限制条件
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
官网解释
park() /park(Object blocker)
阻塞当前线程/阻塞传入的具体线程
阻塞
unpark(Thread thread)
唤醒处于阻塞状态的指定线程
唤醒
主要方法
正常+无锁块要求
解释
之前错误的先唤醒后等待,LockSupport照样支持
成双成对要牢记
LockSupport类中的park等待和unpark唤醒
线程等待唤醒机制
LockSupport与线程中断
你知道什么是Java内存模型JMM吗?
JMM与volatile它们两个之间的关系?(下一章详细讲解)
JMM有哪些特性or它的三大特性是什么?
为什么要有JMM,它为什么出现?作用和功能是什么?
happens-before先行发生原则你有了解过吗?
先从大厂面试题开始
问题?和推导出我们需要知道JMM
计算机硬件存储体系
Java内存模型Java Memory Model
可见性
指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰
原子性
简单案例先过个眼熟
有序性
JMM规范下,三大特性
读取过程
我们定义的所有共享变量都储存在物理主内存中
每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
线程对共享变量所有的操作都必须先在线程自己的工作内存中进行后写回主内存,不能直接从主内存中读写(不能越级)
不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行(同级不能相互访问)
JMM规范下,多线程对变量的读写过程
在JMM中,如果一个操作执行的结果需要对另一个操作可见性或者 代码重排序,那么这两个操作之间必须存在happens-before关系。
x 、y案例说明
先行发生原则说明
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
周一张三周二李四,假如有事情调换班可以的
值日
1+2+3 = 3+2+1
两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
happens-before总原则
一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作;
前一个操作的结果可以被后续的操作获取。讲白点就是前面一个操作把变量X赋值为1,那后面一个操作肯定能知道X已经变成了1。
加深说明
次序规则:
一个unLock操作先行发生于后面((这里的“后面”是指时间上的先后))对同一个锁的lock操作;
锁定规则:
对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。
volatile变量规则:
如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
传递规则:
Thread对象的start()方法先行发生于此线程的每一个动作
线程启动规则(Thread Start Rule):
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
可以通过Thread.interrupted()检测到是否发生中断
线程中断规则(Thread Interruption Rule):
线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行。
线程终止规则(Thread Termination Rule):
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
对象没有完成初始化之前,是不能调用finalized()方法的
说人话
对象终结规则(Finalizer Rule):
happens-before之8条
happens-before小总结
串行化
把getter/setter方法都定义为synchronized方法
把value定义为volatile变量,由于setter方法对value的修改不依赖value的原值,满足volatile关键字使用场景
修复
案例说明
JMM规范下,多线程先行发生原则之happens-before
Java内存模型之JMM
排序要求
特点
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量
所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。
volatile的内存语义
被volatile修改的变量有2大特点
没有管控,顺序难保
上海南京路步行街武警“人墙”当红灯
设定规则,禁止乱序
先说生活case
内存屏障 (Memory Barriers / Fences)
volatile凭什么可以保证可见性和有序性???
上一章讲解过happens-before先行发生原则,类似接口规范,落地?
你凭什么可以保证?你管用吗?
落地靠什么?
在读指令之前插入读屏障,让工作内存或CPU高速缓存中的缓存数据失效,重新回到主存种获取最新数据
读屏障(Load Barrier)
在写指令之后插入写屏障,强制把写缓冲区中的数据刷入主内存中
写屏障(Store Barrier)
粗分:2种
细分:4种
orderAccess_linux_x86.inline.hpp
OrderAccess.hpp
Unsafe.cpp
Unsafe.java
IDEA工具里面找Unsafe.class
C++源码分析
四大屏障分别是什么意思
JVM中提供了四类内存屏障指令
通过内存屏障禁重排
上述说明
禁止重排
什么叫保证有序性?
happens-before 之 volatile 变量规则
1. 在每个 volatile 写操作的前⾯插⼊⼀个 StoreStore 屏障
2. 在每个 volatile 写操作的后⾯插⼊⼀个 StoreLoad 屏障
写
对比图
3. 在每个 volatile 读操作的后⾯插⼊⼀个 LoadLoad 屏障
4. 在每个 volatile 读操作的后⾯插⼊⼀个 LoadStore 屏障
读
JMM 就将内存屏障插⼊策略分为 4 种
如下内容困难,可能会导致学生懵逼,课堂要讲解细致分2次讲解+复习
内存屏障(面试重点必须拿下)
保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可见
不加volatile,没有可见性,程序无法停止
加了volatile,保证可见性,程序可以停止
上述代码原理解释
volatile变量的读写过程
保证可见性
volatile变量的复合操作(如i++)不具有原子性
不保证原子性
从i++的字节码角度说明
读取赋值一个普通变量的情况
volatile主要是对其中部分指令做了处理
既然一修改就是可见,为什么还不能保证原子性?
读取赋值一个volatile变量的情况
JVM的字节码,i++分成三步,间隙期不同步非原子操作(i++)
面试回答
volatile变量不适合参与到依赖当前值的运算
没有原子性
说明与案例
volatile有关的禁止指令重排的行为
StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中。
在每一个volatile写操作前面插入一个StoreStore屏障
StoreLoad屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序
在每一个volatile写操作后面插入一个StoreLoad屏障
LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。
在每一个volatile读操作后面插入一个LoadLoad屏障
LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。
在每一个volatile读操作后面插入一个LoadStore屏障
四大屏障的插入情况
Code说明
volatile的底层实现是通过内存屏障,2次复习
指令禁重排
volatile特性
volatile int a = 10
volatile boolean flag = false
单一赋值可以,but含复合运算赋值不可以(i++之类)
状态标志,判断业务是否结束
开销较低的读,写锁策略
单线程看问题代码
由于存在指令重排序......
多线程看问题代码
问题
加volatile修饰
解决01
面试题,反周志明老师的案例,你还有不加volatile的方法吗
采用静态内部类的方式实现
解决02
DCL双端锁的发布
如何正确使用volatile
内存屏障是什么
阻止屏障两边的指令重排序
写数据时加入屏障,强制将线程私有工作内存的数据刷回主物理内存
读数据时加入屏障,线程私有工作内存的数据失效,重新到主物理内存中获取最新数据
内存屏障能干嘛
内存屏障四大指令
字节码层面
关键字
凭什么我们java写了一个volatile关键字系统底层加入内存屏障?两者关系怎么勾搭上的?
volatile可见性
写指令
读指令
volatile禁重排
对比java.util.concurrent.locks.Lock来理解
一句话总结
最后的小总结(八股小作文)
volatile与Java内存模型
多线程环境不使用原子类保证线程安全(基本数据类型)
多线程环境 使用原子类保证线程安全(基本数据类型)
没有CAS之前
原理
硬件级别保证
CASDemo代码
2019年回答到上面即可,但2020年受到疫情影响,o(╥﹏╥)o
UnSafe
我们知道i++线程不安全的,那atomicInteger.getAndIncrement()
new AtomicInteger().getAndIncrement();
native修饰的方法代表是底层方法
cmpxchg
在不同的操作系统下会调用不同的cmpxchg重载函数,阳哥本次用的是win10系统
底层汇编
CAS底层原理?如果知道,谈谈你对UnSafe的理解
AtomicBook
AtomicOrder
AtomicInteger原子整型,可否有其它原子类型?
AtomicReferenceDemo
原子引用
自己实现一个自旋锁SpinLockDemo
自旋锁,借鉴CAS思想
循环时间长开销很大。
ABA问题怎么产生的
AtomicStampedReference
ABADemo
下一章介绍AtomicMarkableReference
版本号时间戳原子引用
比较+版本号一起
引出来ABA问题???
CAS缺点
CAS(compare and swap)
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicIntegerFieldUpdater
AtomicLong
AtomicLongArray
AtomicLongFieldUpdater
AtomicMarkableReference
AtomicReference
AtomicReferenceArray
AtomicReferenceFieldUpdater
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder
atomic
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
常用API简介
tsleep→countDownLatch
Case
基本类型原子类
基本使用
要求学生自学
数组类型原子类
自旋锁SpinLockDemo
携带版本号的引用类型原子类,可以解决ABA问题
解决修改过几次
状态戳原子引用
原子更新带有标记位的引用类型对象
它的定义就是将状态戳简化为true|false
类似一次性筷子
解决是否修改过
状态戳(true/false)原子引用
引用类型原子类
原子更新对象中int类型字段的值
原子更新对象中Long类型字段的值
原子更新引用类型字段的值
理念
以一种线程安全的方式操作非线程安全对象内的某些字段
使用目的
强制要求:更新的对象属性必须使用 public volatile 修饰符。
因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
使用要求
AtomicIntegerFieldUpdaterDemo
面试官问你:你在哪里用了volatile
对象的属性修改原子类
阿里要命题目
常用API
LongAdder只能用来计算加法,且从零开始计算
LongAccumulator提供了自定义的函数操作
LongAdderAPIDemo
入门讲解
LongAdder高性能对比Code演示
点赞计数器,看看性能
架构
官网说明和阿里要求
LongAdder是Striped64的子类
最重要2个
Striped64有几个比较重要的成员函数
Striped64中一些变量或者方法的定义
Striped64
是 java.util.concurrent.atomic 下 Striped64 的一个内部类
Cell
base变量:非竞态条件下,直接累加到该变量上
Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中
内部有一个base变量,一个Cell[]数组。
数学表达
LongAdder为什么这么快
原理(LongAdder为什么这么快)
1.最初无竞争时只更新base;
2.如果更新base失败后,首次新建一个Cell[]数组
3.当多个线程竞争同一个Cell比较激烈时,可能就要对Cell[]扩容
条件递增,逐步解析
add(1L)
longAccumulate入参说明
线程hash值:probe
总纲
未初始化过Cell[]数组,尝试占有锁并首次初始化cells数组
刚刚要初始化Cell[]数组(首次新建)
多个线程尝试CAS修改失败的线程会走到这个分支
兜底
多个线程同时命中一个cell的竞争
总体代码
1
2
3
4
5
6
上6步骤总结
Cell数组不再为空且可能存在Cell数组扩容
计算
步骤
longAccumulate
为啥在并发情况下sum的值不精确
sum
longAdder.increment()
源码解读深度分析
线程安全,可允许一些性能损耗,要求高精度时可使用
保证精度,性能代价
AtomicLong是多个线程针对单个热点值value进行原子操作
当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用
保证性能,精度代价
LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作
使用总结
源码、原理分析
CAS+自旋
incrementAndGet
低并发下的全局计算
AtomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。
场景
高并发后性能急剧下降
AtomicLong的自旋会成为瓶颈
why?
缺陷
http://blog.palominolabs.com/2014/02/10/java-8-performance-improvements-longadder-vs-atomiclong/
LongAdder vs AtomicLong Performance
CAS+Base+Cell数组分散
空间换时间并分散了热点数据
高并发下的全局计算
sum求和后还有计算线程修改结果的话,最后结果不够准确
原子操作增强类原理深度解析
再分类
原子操作类之18罗汉增强
ThreadLocal中ThreadLocalMap的数据结构和关系?
ThreadLocal的key是弱引用,这是为什么?
ThreadLocal内存泄露问题你知道吗?
ThreadLocal中最后为什么要加remove方法?
......
恶心的大厂面试题
能干嘛
api介绍
群雄逐鹿起纷争
按照总销售额统计,方便集团公司做计划统计
比如某找房软件,每个中介销售都有自己的销售额指标,自己专属自己的,不和别人掺和
不参加总和计算,希望各自分灶吃饭,各凭销售本事提成,按照出单数各自统计
上述需求变化了...
人手一份天下安
上述需求该如何处理???
永远的helloworld讲起
因为每个 Thread 内有自己的实例副本且该副本只由当前线程自己使用
既然其它 Thread 不可访问,那就不存在多线程间共享的问题。
统一设置初始值,但是每个线程对这个值的修改都是各自线程互相独立的
1 加入synchronized或者Lock控制资源的访问顺序
2 人手一份,大家各自安好,没必要抢夺
如何才能不争抢
通过上面代码总结
ThreadLocal简介
官网文档
bugs
源码分析结论
非线程安全的SimpleDateFormat
将SimpleDateFormat定义成局部变量。
缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收。
解决1
ThreadLocal,也叫做线程本地变量或者线程本地存储
解决2
加锁
第3方时间库
其它
学习自学给案例,家庭作业
DateUtils
从阿里ThreadLocal规范开始
再次体会,各自线程,人手一份
Thread和ThreadLocal
ThreadLocal和ThreadLocalMap
All三者总概括
Thread,ThreadLocal,ThreadLocalMap 关系
ThreadLocal源码分析
从阿里面试题开始讲起
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
什么是内存泄漏
再回首ThreadLocalMap
整体架构
新建一个带finalize()方法的对象MyObject
case
强引用(默认支持模式)
软引用
软引用和弱引用的适用场景
弱引用
构造方法
引用队列
虚引用
GCRoots和四大引用小总结
强引用、软引用、弱引用、虚引用分别是什么?
关系
谁惹的祸?
为什么源代码用弱引用?
埋雷
key为null的entry,原理解析
expungeStaleEntry
set()
get()
remove()
set、get方法会去检查所有键为null的Entry对象
弱引用就万事大吉了吗?
为什么要用弱引用?不用如何?
定义
如何定义
用完记得手动remove
最佳实践
ThreadLocal内存泄露问题
ThreadLocal 并不解决线程间共享数据的问题
ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景
ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
每个线程持有一个只属于自己的专属Map并维护了ThreadLocal对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题
ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocal对象无法被回收的问题
群雄逐鹿起纷争,人各一份天下安
聊聊ThreadLocal
同学反馈2020.6.27
先从阿里及其它大厂面试题说起
JVM里堆→新生区→伊甸园区
位置所在
头体?想想我们的HTML报文
构成布局
Object object = new Object()谈谈你对这句话的理解?一般而言JDK8按照默认情况下,new一个对象占多少内存空间
周志明老师JVM第3版
权威定义
它保存什么
默认存储对象的HashCode、分代年龄和锁标志位等信息。这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。
对象标记Mark Word
参考尚硅谷宋红康老师原图
对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
类元信息(又叫类型指针)
在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。
对象头多大
对象头
存放类的属性(Field)数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
实例数据
虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐这部分内存按8字节补充对齐。
对齐填充
对象在堆内存中的存储布局
http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
Hotspot术语表官网
http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/89fb452b3688/src/share/vm/oops/oop.hpp
底层源码理论证明
官网理论
对象在堆内存中布局
32位(看一下即可,不用学了,以64位为准)
markOop.hpp
oop.hpp
markword(64位)分布图,对象布局、GC回收和后面的锁升级就是对象标记MarkWord里面标志位的变化
64位重要
再说对象头的MarkWord
http://openjdk.java.net/projects/code-tools/jol/
POM
JOL官网
小试一下
JOL证明
结果呈现说明
-XX:MaxTenuringThreshold=16
try try
GC年龄采用4位bit存储,最大为15,例如MaxTenuringThreshold参数默认值就是15
java -XX:+PrintCommandLineFlags -version
-XX:+UseCompressedClassPointers
结果
默认开启压缩说明
上述表示开启了类型指针的压缩,以节约空间,假如不加压缩???
-XX:-UseCompressedClassPointers
手动关闭压缩再看看
命令
尾巴参数说明
聊聊Object obj = new Object()
换成其他对象试试
Java对象内存布局和对象头
谈谈你对Synchronized的理解
Synchronized的锁升级你聊聊
Synchronized的性能是不是一定弱于Lock
同学反馈2020.6.17
synchronized锁:由对象头中的Mark Word根据锁标志位的不同而被复用及锁升级策略
本章路线总纲
重量级锁,假如锁的竞争比较激烈的话,性能下降
Java5之前,用户态和内核态之间的切换
java5以前,只有Synchronized,这个是操作系统级别的重量级操作
Monitor(监视器锁)
结合之前的synchoronized和对象头说明
为什么每一个对象都可以成为一个锁????
Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁
需要有个逐步升级的过程,别一开始就捅到重量级锁
java6开始,优化Synchronized
Synchronized的性能变化
只有一个线程来访问,有且唯一Only One
有2个线程A、B来交替访问
竞争激烈,多个线程来访问
多线程访问情况,3种
synchronized用的锁是存在Java对象头里的Mark Word中锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位
64位标记图再看
升级流程
Code演示
程序不会有锁的竞争
无锁
比如线程卖票中的a线程A线程因为获得最早获得锁,所以在后续竞争中更容易获取锁
当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁
同一个老顾客来访,直接老规矩行方便
看看多线程卖票,同一个线程获得体会一下
小结论
主要作用
通过CAS方式修改markword中的线程ID
细化案例Account对象举例说明
偏向锁的持有
java -XX:+PrintFlagsInitial |grep BiasedLock*
重要参数说明
偏向锁JVM命令
演示无效果
一切默认
因为参数系统默认开启
-XX:BiasedLockingStartupDelay=0
关闭延时参数,启用该功能
开始有第2个线程来抢夺了
好日子终会到头......o(╥﹏╥)o
当有另外线程逐步来竞争锁的时候,就不能再使用偏向锁了,要升级为轻量级锁
竞争线程尝试CAS更新对象头失败,会等待到全局安全点(此时不会执行任何代码)撤销偏向锁。
撤销
偏向锁的撤销
总体步骤流程图示
Java15逐步废弃偏向锁
偏锁
多线程竞争,但是任意时刻只有一个线程竞争,即不存在竞争太过激烈的情况,也就没有线程阻塞
有线程来参与锁的竞争,但是获取锁的冲突时间极短
本质就是自旋锁
轻量级锁的获取
如果关闭偏向锁,就可以直接进入轻量级锁
-XX:-UseBiasedLocking
步骤流程图示
-XX:PreBlockSpin=10来修改
默认启用,默认情况下自旋的次数是 10 次
或者自旋线程数超过cpu核数一半
上述了解即可,别用了。
java6之前
自适应意味着自旋的次数不是固定不变的
同一个锁上一次自旋的时间。
拥有锁线程的状态来决定。
而是根据:
自适应
Java6之后
自旋达到一定次数和程度
争夺轻量级锁失败时,自旋尝试抢占锁
轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁
轻量锁与偏向锁的区别和不同
轻锁
有大量的线程参与锁的竞争,冲突性很高
锁标志位
重锁
锁升级以后hashcode去哪里了
各种锁优缺点、synchronized锁升级和实现原理
synchronized锁种类及升级步骤
Just In Time Compiler,一般翻译为即时编译器
JIT
逃逸分析
LockClearUPDemo
锁消除
LockBigDemo
锁粗化
JIT编译器对锁的优化
Synchronized与锁升级
可重入锁
自旋锁
LockSupport
数据结构之链表
设计模式之模板设计模式
前置知识
抽象的队列同步器
源代码
字面意思
官方解释
是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态
技术解释
和AQS有关的
定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可。
锁,面向锁的使用者
比如Java并发大神DougLee,提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。
同步器,面向锁的实现者
进一步理解锁和同步器的关系
AQS为什么是JUC内容中最重要的基石
有阻塞就需要排队,实现排队必然需要队列
加锁会导致阻塞
解释说明
AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改。
AQS初识
AQS的同步状态State成员变量
零就是没人,自由状态可以办理
大于等于1,有人占用窗口,等着去
银行办理业务的受理窗口状态
AQS的int变量
CLH队列(三个大牛的名字组成),为一个双向队列
银行候客区的等待顾客
AQS的CLH队列
state变量+CLH双端队列
AQS自身
volatile int waitStatus
Node的等待状态waitState成员变量
等候区其它顾客(其它线程)的等待状态
队列中每个排队的个体就是一个 Node
Node的int变量
内部结构
属性说明
Node此类的讲解
内部类Node(Node类在AQS类内部)
AQS内部体系架构
AQS同步队列的基本结构
AQS初步
Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的
ReentrantLock的原理
从最简单的lock方法开始看看公平和非公平
本次讲解我们走非公平锁作为案例突破口
源码解读比较困难,别着急---阳哥的全系列脑图给大家做好笔记
lock()
源码和3大流程走向
acquire()
继续推进条件,走下一个方法
return false;
结束
return true;
nonfairTryAcquire(acquires)
本次走非公平锁
tryAcquire(arg)
enq(node);
双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。
addWaiter(Node mode)
prev
compareAndSetTail
next
假如3号ThreadC线程进来
addWaiter(Node.EXCLUSIVE)
shouldParkAfterFailedAcquire 和 parkAndCheckInterrupt 方法中
如果前驱节点的 waitStatus 是 SIGNAL状态,即 shouldParkAfterFailedAcquire 方法会返回 true 程序会继续向下执行 parkAndCheckInterrupt 方法,用于将当前线程挂起
shouldParkAfterFailedAcquire
parkAndCheckInterrupt
假如再抢抢失败就会进入
acquireQueued
源码解读走起
非公平锁走起,方法lock()
unparkSuccessor
tryRelease(arg)
sync.release(1);
unlock
从我们的ReentrantLock开始解读AQS
AbstractQueuedSynchronizer之AQS
你知道Java里面有哪些锁?
你说你用过读写锁,锁饥饿问题是什么?
StampedLock知道吗?(邮戳锁/票据锁)
ReentrantReadWriteLock有锁降级机制策略你知道吗?
关于锁的大厂面试题
读写锁说明
再说说演变
『读写锁』意义和特点
可重入
读写分离
code演示ReentrantReadWriteLockDemo
无锁无序→加锁→读写锁演变复习
《Java 并发编程的艺术》中关于锁降级的说明:
锁降级:将写入锁降级为读锁(类似Linux文件读写权限理解,就像写权限要高于读权限一样)
锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性
可以降级
如果有线程在读,那么写线程是无法获取写锁的,是悲观锁的策略
code演示LockDownGradingDemo
不可锁升级
读写锁降级演示
写锁和读锁是互斥的
从写锁→读锁,ReentrantReadWriteLock可以降级
Oracle公司ReentrantWriteReadLock源码总结
读写锁之读写规矩,再说降级
请你简单聊聊ReentrantReadWriteLock
面试题:有没有比读写锁更快的锁?
StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化。
也叫票据锁
邮戳锁
代表了锁的状态。当stamp返回零时,表示线程获取锁失败。并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值。
stamp(戳记,long类型)
锁饥饿问题
new ReentrantReadWriteLock(true);
使用“公平”策略可以一定程度上缓解这个问题
但是“公平”策略是以牺牲系统吞吐量为代价的
如何缓解锁饥饿问题?
Why闪亮
StampedLock类的乐观读锁闪亮登场
它是由锁饥饿问题引出
所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功;
所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;
StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
①Reading(读模式):功能和ReentrantReadWriteLock的读锁类似
②Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
③Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式
StampedLock有三种访问模式
读的过程中也允许获取写锁介入
乐观读模式code演示
StampedLock的特点
StampedLock 不支持重入,没有Re开头
StampedLock 的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意。
如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()
使用 StampedLock一定不要调用中断操作,即不要调用interrupt() 方法
StampedLock的缺点
邮戳锁StampedLock
ReentrantLock、ReentrantReadWriteLock、StampedLock讲解
可重入锁(递归锁)
公平锁/非公平锁
死锁
偏向锁
轻量锁
重量锁
邮戳(票据)锁
“锁”事儿
JMM
锁的到底是什么
64位图
synchronized及升级优化
CAS的底层原理
ABA问题
CAS问题
CAS
特性
内存屏障
LockSupport.park和Object.wait区别
出队入队Node
AbstractQueuedSynchronizer
ThreadLocal
原子增强类
终章の回顾
课程总结与回顾
JUC并发编程与源码分析2021.3V2.6
0 条评论
回复 删除
下一页