推荐阅读
阅读本文以前,建议先阅读 深刻讲解并发编程模型之概念篇 了解什么是重排序、什么是内存屏障、什么是 happens-before。否则下面的内容阅读起来有点费劲。编程
一个线程的操做结果对其它线程可见成为可见性缓存
在Java中主要是使用了volatite修饰的变量,那么就能够保证可见效。工做原理以下:多线程
lock前缀指令和MESI协议综合使用
对于volatile修饰的变量,执行写操做的话,JVM会发送一条lock前缀指令给CPU,CPU在计算完以后会当即将这个值写回主内存,同时由于有MESI缓存一致性协议,因此各个CPU都会对总线进行嗅探本身本地缓存中的数据是否被修改了。若是发现某个缓存的值被修改了,那么CPU就会将本身本地缓存的数据过时掉,而后这个CPU上执行的线程在读取那个变量的时候,就会从主内存从新加载最新的数据了。并发
使用lock前缀指令和MESI协议综合使用保证了可见性。app
synchronized主要对变量读写,或者代码块的执行进行加锁,在未释放锁以前,其它现场没法操做synchronized修饰的变量或者代码块。而且,在释放锁以前会讲修改的变量值写到内存中,其它线程进来时读取到的就是新的值了。post
原子性表示一步操做执行过程当中不容许其余操做的出现,直到该操做的完成。
在Java中,对基本数据类型变量的赋值操做是原子性操做。可是对于复合操做是不具备原子性的,好比:优化
int a = 0; // 具备原子性 a++; // 不具备原子性,这个是复合操做,先读取a的值,再进行+1操做,而后把+1结果写给a int b = a; // 这个也不具备原子性,先读取a,而后把b值设为a
在Java的JMM模型中,定义了八种原子操做:spa
程序执行的顺序按照代码的前后顺序执行代码的执行步骤有序线程
在Java中,处理器和编译器会对指令进行重排序的。可是这个重排序只是对单个线程内程序执行结果没有影响,在多线程环境下可能就有影响了。code
int a = 10; // 1 int b = 12; // 2 a = a + 1; // 3 b = b * 2; // 4
实际上,在单线程环境中,程序1和2执行的顺序对程序结果没有影响,程序3和4执行顺序对程序执行结果没有影响,它们是能够在编译器或者处理的优化下作指令重排的,可是程序3不会在程序1以前执行,由于这会影响程序执行结果。具体关于指令重排序,推荐阅读 [深刻讲解并发编程模型之重排序篇
](http://www.funcodingman.cn/po... 。
boolean flag = true; flag = false; // 0 int a = 0; //线程1执行 一、2 代码 a = 1 // 1 flag = true; // 2 //线程2执行 三、四、五、6 代码 while(!flag){ // 3 a = a + 1; // 4 } // 5 System.out.println(a); // 6
此时,若是有两个现场执行该段代码,按照咱们编写的代码逻辑思路是,先执行一、2,再执行三、四、五、六、7。可是在多线程环境中,若是指令进行了重排序,致使2先在0以前执行,那么就会致使预期输出a是2,那么实际是1。
因此,在Java中,咱们须要经过valotite、synchronized对程序进行保护,防止指令重排序让程序输出不是预期的结果。
在Java中,编译器和处理器要想对指令进行重排序,若是程序符合下面的原则,就不会发生重排序,这是JMM强制要求的。
若是程序不知足这四大原则的话,原则上是能够任意重排序的。
Load对应JMM中的加载数据的意思。
语法格式:
// load1表示加载指令1,load2表示加载指令2 load1: LoadLoad :load2
LoadLoad屏障:load1;LoadLoad;load2,确保load1数据的装载先于load2后全部装载指令,也就是说,load1对应的代码和load2对应的代码,是不能指令重排的
Store对应JMM中存储数据在线程本地工做内存的意思
语法格式:
store1;StoreStore;store2
StoreStore屏障:store1;StoreStore;store2,确保store1的数据必定刷回主存,对其余cpu可见,先于store2以及后续指令
语法格式:
load1;LoadStore;store2
LoadStore屏障:load1;LoadStore;store2,确保load1指令的数据装载,先于store2以及后续指令
语法格式:
store1;LoadStore;load2
StoreLoad屏障:store1;StoreLoad;load2,确保store1指令的数据必定刷回主存,对其余cpu可见,先于load2以及后续指令的数据装载
那么volatile修饰的变量,如何在内存屏障中体现的呢?
看一段代码:
volatile a = 1; a = 2; // store操做 int b = a // load操做
对于volatile修改变量的读写操做,都会加入内存屏障
因此,上面代码的伪指令代码:
volatile a = 1; // 声明一个a变量,值为1 StoreStore; // 禁止上面的a = 1和a=2重排 a = 2; StoreLoad; // 确保a的值刷回主内存,对全部CPU可见,下面的读操做才会执行 int b = a;
这里和你们详细分析了并发三大特性问题,分别是可见效、原子性和有序性,以及在Java中如何保证这三大特性,具体的原理是什么。
推荐阅读