第三章 Java内存模型
3.1 内存模型基础
3.1.1 并发编程的两个关键问题
- 线程之间如何通信
java采用共享内存模型隐式通信 - 线程之间如何同步
共享内存模型模型需要显式指定同步
3.1.2 内存模型抽象结构
3.1.3 从源代码到指令序列的重排序
3.1.5 happens-before
JSR-133内存模型采用了happens-before概念,指前一个操作执行的结果对后一个操作可见,且前一个操作按顺序排在后一个操作之前。但执行次序不一定。另外,这两个操作可以在在同一线程也可以不在。
happens-before常用规则
- 程序顺序规则
同一线程的操作顺序
- 监视器锁规则
一个锁的解锁h-b加锁
- volatile变量规则
volatile的写h-b读
- 传递性
3.2重排序
3.2.1数据依赖性
两操作访问同一变量且其中一操作为写操作。
编译器和处理器重排序不能改变存在数据依赖关系的两操作的执行结果(编译器和处理器只考虑单处理器和单线程里的数据依赖性)
3.2.2 as-if-serial
重排序不影响单线程程序执行结果
3.2.4 重排序与多线程
重排序可能会破坏多线程的语义,破坏数据依赖性和控制依赖性(处理器猜测执行,提前读取计算结果保存在重排序缓冲,然后判断后写入变量)
3.3 顺序一致性
如果多线程程序正确同步,则程序的执行具有顺序一致性。即程序执行结果和它在顺序一致性内存模型里的执行结果相同。
这里的同步广义,包括常用原语synchronized volatile final
3.4 volatile
3.4.1 volatile的特性
-可见性 对一个volatile的读,总能看到任意线程对这个volatile变量最后的写入
-原子性 对任意单个volatile变量的读/写具有原子性,但是volatile++没有原子性
3.4.3 volatile的写-读的内存语义
- 写的内存语义:写一个volatile变量时,JMM(java内存模型)会把该线程对应的本地内存中的共享变量值刷新到主内存
- 读的内存语义:读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量
合在一起,线程B读一个volatile变量后,线程A写volatile变量之前的所有可见共享变量的值均对线程B可见
volatile写和锁释放具有相同的内存语义,volatile读和锁获取具有相同的内存语义
3.4.4 volatile的内存语义的实现
- 如果第二个操作是volatile写,不能重排序(volatile写之前插入StoreStore屏障)
- 如果第一个操作是volatile读,不能重排序(volatile读之后插入LoadStore屏障)
- 如果第一个操作是volatile写,第二个操作是volatile读,不能重排序(volatile写之后插入StoreLoad屏障,volatile读之前插入LoadLoad屏障)
3.5 锁的内存语义
3.5.1 锁释放获取的内存语义
一个锁的解锁happens-before于随后对它的加锁
线程释放锁时JMM会将该线程对应的本地内存中的共享变量刷新到主内存
线程获取锁时,JMM会把该线程对应的本地内存置为无效,然后从主内存读取共享变量
volatile写和锁释放具有相同的内存语义,volatile读和锁获取具有相同的内存语义
3.5.2 锁内存语义的实现
以重入锁ReentrantLock为例,它包括公平锁和非公平锁
ReentrantLock依赖于java同步器框架AQS,后者使用volatile来维护同步状态
- 公平锁和非公平锁释放时都要写volatile变量
- 公平锁获取时,首先volatile读
- 非公平锁获取时,首先用compareAndSet更新volatile变量,这个CAS操作同时具有volatile读和volatile写的内存语义。
原因:CAS的处理器代码中,如果是多处理器,就为cmpxchg指令加上lock前缀
* lock通过缓存锁定的方式保证读改写指令执行的原子性
* lock禁止该指令和前后的读写指令的重排序
* lock把写缓冲区的所有数据刷新到内存里