解决Java并发编程中的有关可见性和有序性问题 --- Java 内存模型
Java 内存模型
Java 内存模型规范了JVM如何按需禁用缓存和编译器优化的方法;包括三个关键字volatile、synchronized、final以及Happens-Before规则
volatile
在C语言中原始定义就是禁止使用CPU缓存;对于volatile修饰的变量读写,不能使用CPU缓存,必须从内存中读取或者是写入
示例代码
// 以下代码来源于【参考1】
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
// 这里x会是多少呢?
}
}
}
运行结果:Java 1.5 之前可能是 42 或者 0 ,1.5之后是 42
实现原理
- Lock 前缀指令会引起处理器缓存回写到内存中
- 一个处理器的缓存回写内存会导致其他处理器的缓存无效
volatile 内存语义的实现
- 在volatile变量写操作之前插入StoreStore屏障 --- 禁止变量之前的普通写操作与变量的写操作重排序
- 在volatile变量写操作之后插入StoreLoad屏障 --- 禁止变量之后的可能的volatile写重排序(保守设置)
- 在volatile变量读操作之前插入LoadLoad屏障 --- 禁止变量之后的普通读操作与修饰的变量重排序
- 在volatile变量读操作之前插入LoadStore屏障 --- 禁止变量之后的可能的volatile变量读操作重排序(保守设置)
volatile使用优化
针对缓存行是64字节,并且对共享变量频繁的写在使用volatile变量时要补充64字节
Happens-Before 原则
定义
- 如果一个操作Happens-Before另一个操作,那么前面一个操作的结果对后一个操作可见,而且第一个操作的顺序在第二操作之前
- 如果重排序后执行结果不会导致Happens-Before 语义变化那么允许这种重排序
六条规则
- 程序的顺序性:一个线程中,按照程序顺序,前面的操作Happens-Before之后的操作
- volatile规则:读一个volatile变量的写操作,Happens-Before之后对这个变量的读操作
- 传递性:指如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。
- 管程中锁的规则:对一个锁的解锁,Happens-Before 随后对这个锁的加锁
- 线程start规则:如果线程A调用线程B的 start() 方法,那么该start方法操作Happens-Before线程B中的任意操作
- 线程Join规则:如果线程A调用线程B的join方法并成功返回,那么线程B中的任意操作Happens-Before于该join操作的返回
final
- 在构造器内对一个final域的写入,与随后把这个构造对象的引用赋值给一个引用变量,之间的操作不能重排序
- JMM禁止将对final域的写排序到构造求之外
- 编译器会在final写之后,构造函数return之前插入StoreStore屏障
- 初次读一个包含final域的对象的引用,与随后读这个final域不能重排序
