1. JMM 理解
前提:并发编程有3大问题,可见性、有序性、原子性。
导致可见性
的原因是缓存,有序性
的原因是 编译器优化。解决方法就是直接禁用缓存和编译器优化,导致程序性能堪忧。
因此合理的方案就是按需
禁用缓存和编译器优化。
JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。
所以: java 内存模型针对在多线程环境下,
可见性
有序性
制定了一些规范,在jvm 层面 提供按需禁用缓存和编译优化的方法。具体就是使用synchronized,volatile,final三个关键字和Happens-before 规则。 解决并发编程中 的代码的有序性和可见性。
long double 32位原子性问题。
如果需要保证 long 和 double 在 32 位系统中原子性,需要用 volatile 修饰
load load 两个连续的读不要重排序 防止读跑上去
防止 B 的 Load 重排到 A 的 Load 之前
if(A) {
LoadLoad
return B
}
read(A)
LoadLoad
read(B)
- 意义:A == true 时,再去获取 B,否则可能会由于重排导致 B 的值相对于 A 是过期的
loadstore 防止连续 读写 防止写跑上去。
Acquire 第一个是load,防止后续的 读、写 不要重排序
同一线程内,读 操作之后的的读写,上不去,同时第一个load能读到主存中的最新值。
store store 连续的写 防止写跑下来
- 防止 A 的 Store 被重排到 B 的 Store 之后
A = x
StoreStore
B = true
- 意义:在 B 修改为 true 之前,其它线程别想看到 A 的修改
- 有点类似于 sql 中更新后,commit 之前,其它事务不能看到这些更新(B 的赋值会触发 commit 并撤除屏障)
- 有点类似于 sql 中更新后,commit 之前,其它事务不能看到这些更新(B 的赋值会触发 commit 并撤除屏障)
release 防止读和写 跑下来。 ss ls
同一线程内,写操作之前的
, 读写下不来,后面的store 都能将改动的 都写入主存。
store store
load sore
防止上面的 读操作 写操作,被重排序到 写操作下面
store load ** 发生在线程切换时有效。 保证可见性。
sotre load 屏障 在线程切换时,保证可见性。
意义:屏障前的改动都同步到主存,屏障后的 Load 获取主存最新数据
- 防止屏障前所有的写操作,被重排序到屏障后的任何的读操作,可以认为此
store -> load
是连续的 - 有点类似于 git 中先 commit,再远程 poll,而且这个动作是原子的
2.volatile 本质
写变量时 加 loadstore store store 屏障 和 store load 屏障
红色 蓝色是两个线程。
读取变量时 load load ,load store
在变量加入load load load store 屏障
1. 保证单一变量赋值的原子性
32 位操作系统,long double
2. 保证变量的有序性
线程内通过内存屏障保证有序,线程切换按照happen-before 有序。
partial ordering
total ordering
3. 可见性
线程切换时 ,发生了写->读,则变量可见,顺便影响普通变量可见性。
在volatile 变量 加入store load 屏障。
红色线程的写入同步到主存,然后让蓝色线程的读取,取主存中读取最新值。
3. Synchronization order
线程内部的一个顺序
4.happen-before 原则
在线程切换时
,代码的顺序 和可见性。
表达的是,前一个线程的操作的结果 对后续线程的操作是可见的。
线程启动 和运行边界
线程1 启动线程2前对共享变量进行修改,在线程2运行时,读取共享变量一定能看到修改。
线程的结束 和join 的边界。
线程1 结束前,对共享变量的修改。t2线程等待t1线程的解释 join,t2也能读取到共享变量的读取。
线程的打断 和得知线程的打断。
interrupt
t1线程修改共享变量,t1线程对t2线程打断,t2线程得知打断,能够读取到共享变量的改变。
unlock 和lock 边界。
t1线程解锁前对共享变量的修改,t2线程加锁后,能够读取到共享变量的修改。
volatile 对变量的写 和volatile 的读 的边界
传递性
如果a hb b,
b hb c,
那么有a hb c
5. cas 原理
使用乐观锁机制,保证变量读写的原子性。
volatile+ cas 实现原子 可见 有序
volatile 搭配cas 保证 共享变量的可见性。
线程数小于cpu 核心数,乐观锁性能高。
aqs 的state 使用volatile
cas + volatile 保证 state 的最新值 和 互斥。
concurrenthashmap 懒惰初始化
有一个sizectl 属性,volatile 修饰,保证可见性。
在第一次 put 时,检查sizectl,使用cas 把 0 改成 -1, (-1,hash表正在被初始化),
其他线程来 初始化 cas 失败,不会重复创建了。
6. synchronized
有序性
在monitor enter 和 monitor exit 加了相应的屏障,保证了 同步代码块 内部的代码,共享变量的读写, 不会重排序到同步代码块的外面。
强调
:
但是 在同步代码块内部
,还是会存在重排序
的。
7 反射
反射可见 对象自己的 类名、接口、父类、成员变量、方法。
类加载 收集 类的信息,记录在instance klass中
反射是直接获取加载了方法区的类模板,所以可以拿到整个类的所有东西,看似是打破了封装特性,但实际上反射的真正作用是为了解偶以及实现动态性
8 cas
CAS(Compare and Swap)是一种并发控制机制,用于解决多线程环境下的原子性操作问题。它主要包含以下步骤:
比较
:首先,CAS操作会比较某个内存位置的当前值与预期值是否相等。
交换
:如果当前值与预期值相等
,CAS操作会将新值写入该内存位置
。否则,表示其他线程已经修改了该内存位置的值,CAS操作失败。
返回结果:CAS操作返回一个标识,指示操作是否成功。
CAS操作是原子的
,即在执行过程中不会被其他线程中断,并且能够确保操作的原子性。因此,CAS通常用于实现线程同步、无锁算法以及乐观锁等。
在实现上,CAS依赖底层硬件提供的原子指令,如x86处理器上的CMPXCHG指令。通过这些原子指令,CAS可以在一个操作中同时进行比较和交换操作,避免了传统锁机制所需的上下文切换和线程阻塞的开销。文章来源:https://www.toymoban.com/news/detail-534366.html
需要注意的是,尽管CAS操作可以避免锁带来的开销,但它并不能解决所有并发问题。在高并发场景下,由于CAS的自旋操作可能导致大量的CPU资源消耗,因此需要根据具体情况权衡使用。此外,CAS操作在并发度较高、竞争激烈的情况下可能会引发ABA问题,需要额外的措施进行处理(如使用版本号或标记位)来确保数据的一致性。
#9 synchronzied
锁升级过程
文章来源地址https://www.toymoban.com/news/detail-534366.html
到了这里,关于并发编程_jmm部分的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!