1. JMM 理解
前提:并发编程有3大问题,可见性、有序性、原子性。
导致可见性
的原因是缓存,有序性
的原因是 编译器优化。解决方法就是直接禁用缓存和编译器优化,导致程序性能堪忧。
因此合理的方案就是按需
禁用缓存和编译器优化。
所以: 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 加了相应的屏障,
保证了 同步代码块 内部的代码,共享变量的读写, 不会重排序到同步代码块的外面。
强调
:
但是 在同步代码块内部
,还是会存在重排序
的。