Java内存模型(JMM)
JMM是为了保证多个线程间可以有效地正确地协同工作而诞生的一种规则
JMM的关键技术围绕着多线程的原子性,可见性,有序性来建立的
原子性
原子性只一个操作时不可中断的,即使是在多线程的环境下,一个操作一旦开始,就不会被其他线程干扰。比如对于一个静态全局变量int i,线程A给它赋值1,线程B给它赋值-1,则不管这连个线程以何种方式赋值,i的值要么是1,要么是-1,线程A和B之间没有干扰。
但是我们不使用int类型而使用long类型的话,对于32的系统来说long类型的数据是不具有原子性的,因为long有64位,也就是说两个线程同时对long进行读取或写入的话,对线程之间的结果是有干扰的。
可见性
可见性是指当一个线程修改了某一个共享变量的值,其他行程能否立即知道这个修改。显然对于串行程序来说可见性问题是不存在的,因为在一个操作步骤中修改了某个变量,那么在后续的步骤中,读取这个变量的值,一定是修改后的新值。
而这个问题在并发程序中就不见得是这样了,缓存优化或者硬件优化,指令重排以及编辑器的优化,都可能导致一个线程的修改不会立即被其他线程察觉。
有序性
在并发时,程序的执行可能会发生乱序,给人的直观感觉就是写在前面的代码会放到后面去执行。有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。
但是指令重排是有基本前提的,它保证了串行语义的一致性,不会是串行的语义逻辑发生问题,可没有义务保证多线程的语义也是一致的。那么为什么要进行指令重拍呢?其实完全是出于性能考虑,指令重排对于提高cpu处理性能是十分必要的,虽然带来了乱序的问题,但是这点牺牲是完全值得的。
哪些指令不能重排
虽然Java虚拟机和执行系统会对指令进行一定的重排,但是指令重排是有原则的,并非所有的指令都可以任意修改其执行顺序,以下罗列了一些基本原则,这些原则不可违背。
1 程序顺序原则:一个线程内保证语义的串行性
2 volatile原则:volatile的写,先发生于读,这保证了volatile变量的可见性
3 锁规则:解锁必然发生在加锁前
4 传递性:A先于B,B先于C,那么A必先于C
5 线程的start方法先于它的每一个动作
6 线程的所有操作先于线程的终结(Thread.join( ))
7 线程的中断先于被中断的代码
8 对象的构造函数的执行,结束先于finalize( )方法
以程序顺序原则为例,重拍后的指令绝对不能改变原有的串行语义,比如
a=1;
b=a+1;
由于第二条语句依赖第一条语句的结果,如果冒然交换两条语句的顺序,那么程序的语义就会被修改,因此这种情况是绝对不能发生的。也是指令重排的一条基本原则。