从字面上理解JMM(Java Memory model)就是java定义了一个底层内存操作的一个抽象。
当多线程同时对共享变量进行操作时,会存在原子性,可见性,有序性问题。JMM抽象定义了每个线程有自己的本地内存,共享变量存在主内存中,JMM通过控制主内存与线程的本地内存的交互来保证线程之间的可见性。
通过抽象JMM可以让程序员使用JMM定义的规则合理的按需禁用缓存、编译器优化,从源头上解决可见性、重排序问题。
JMM可以从源头上解决可见性、重排序问题,而它的核心部分就是happens-before与as-if-serial规则,只要充分理解happens-before,程序员就能理解透并发编程中的可见性问题,只要充分理解as-if-serial,程序员就能理解透并发编程中的重排序问题。
happens-before规则是用来描述操作之间的内存可见性问题,在JMM里,如果一个操作对另外一个操作可见,两个操作之间就必须要保证happens-before关系,这里提到的两个操作可以是在一个线程内,也可以是不同线程中。
做了这么多铺垫,我们来看看happens-before规则到底是什么,其实很简单,总共只有7条规则,和一个特性。(再强调一次:两个操作具有happens-before规则不意味前一个操作再另一个操作之前执行,而是前一操作对另一操作可见,且前一操作顺序排在第二个操作之前)
2.1.1:七个规则
1、程序顺序规则:一个线程中的每个操作,happens-before该线程中的任意后续操作。
2、监视器锁规则(涉及Java中的关键字其实就是synchronized):对一个锁的解锁,happens-before随后对这个锁的加锁。
3、volatile规则:对一个volatile变量的写,happens-before于任意后续对这个volatile变量的读。
4、线程启动规则:Thread对象的start()方法happens-before于此线程的每一个动作。
5、线程终止规则:线程中所有操作都happens-before对此线程的终止操作。
6、线程中断规则:对线程interrupt()方法的调用happens-before于被中断线程的代码检测到中断事件的发生。
7、对象终结规则:一个对象的初始化happens-before它的finalize()方法的开始。
2.1.2:一个特性
这条规则是指如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
as-if-serial语义规定在单线程中无论怎么重排序,程序的执行结果不能被改变。因为两个操作存在数据依赖as-if-serial会禁止重排序。
如果上表这三种情况出现重排序时,会改变执行结果。
2.3:上代码实践
int a=0;
boolean flag=false;
public void write(){
//1 a=2;
//2 flag=true;
}
public void read(){
//3 if(flag){
//4 System.out.println(a);
}
}
启动线程a和线程b,线程a执行write,然后线程b执行read(),由于操作1,2没有依赖性,当1,2进行重排序,此时线程b看不到a的改变。
int a=0;
volatile boolean flag=false;
public void write(){
//1 a=2;
//2 flag=true;
}
public void read(){
//3 if(flag){
//4 System.out.println(a);
}
根据happens-before 程序次序规则,1 happens-before 2;3 happens-before 4。
根据happens-before volatile规则 ,2 happens-before 3
根据happens-before 传递下特性 ,此时可以推断出 1 happens-before 4
当使用volatile关键字,此时happens-before规则向程序员保证 b一定可以看到a线程写数据的操作,在使用多线程时就不会留下安全隐患。
通过以上案例,可以发现我们只要理解透JMM,对以后并发编程出现的问题就可以通过推导判断。而JMM底层实现,是通过内存屏障(memory barrier)禁止重排序实现的,如果对这块感兴趣后面文章会讲解。