推薦閱讀
閱讀本文以前,建議先閱讀 深刻講解併發編程模型之概念篇 瞭解什麼是重排序、什麼是內存屏障、什麼是 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中如何保證這三大特性,具體的原理是什麼。
推薦閱讀