java 多執行緒下的原子操作了解認識
public class Test {
boolean flag= false;
public void changeFlag(){
flag = true;
}
public void execute(){
if(flag){
System.out.println("execute....");
}
}
}
首先看上述程式碼:很簡單,但是在這裡如果有:執行緒A執行changeFlag方法之後,執行緒B再執行execute方法,試問,execute方法會不會打印出:execute....呢? (是的,這個是多執行緒的案例,由他來引入;)
答案是:不一定會;
原因:
一般情況下,會執行列印的方法,就說說不會列印的理由,
執行緒A在執行完flag = true;之後,還未完全退出執行緒A,這時執行緒B搶到CUP資源,開始執行execute方法,判斷flag的值時,flag的值依然為false;
因為在java記憶體模型中,多執行緒之間的變數值是不可見的;每個執行緒都有自己獨立的working memory(工作記憶體),裡面儲存該執行緒使用到的變數的副本;程式在執行之前,所有變數都存在主記憶體當中,執行緒記憶體會往主記憶體中拷貝一份變數的副本,執行緒執行結束後,會將副本變數值賦給主記憶體中對應的變數,然後主記憶體再將修改後的變數賦值到每個執行緒的副本中;所以執行緒之間變數值的傳遞需要通過主記憶體完成;而執行緒之間的變數(全域性)是不能互相訪問的;
回到當前例項,所以這就是執行緒A中的flag為false的原因;
在這裡涉及到java記憶體模型知識;針對上述例子大致說說原因,推薦連結:https://www.cnblogs.com/rocomp/p/4780532.html
通過上述例子餘留了一個問題:如何才能使得執行緒之間的變數(全域性)可見?
java語言支援可見性的實現方法:
synchronize、volatile、final
這裡主要說說volatile:
如果在上述例子中flag宣告前加上volatile;那麼答案就是肯定的了;
但是volatile就可以萬能了?當然不是:如下例子:
public class Test{ public volatile int i=0; public void test1(){ for(i;i<1000;i++){ system.out.println(i); } } }
上述程式碼,在多執行緒執行該方法的情況下,是否會列印到1000呢,如果不會可以加到10000或更大,然後讓執行緒睡三秒,執行結果卻不是我們想要的(1.到1000依次列印);
列印結果坑定少於1000,那麼為什麼會出現此問題呢?
這裡就是原子操作的原因:For Example 例如:
以下多執行緒對int型變數x的操作,哪幾個需要進行同步:( )
A. x=y; B. x++; C. ++x; D. x=1;
博文轉載:https://blog.csdn.net/encoder1234/article/details/52228224
看了上面的連結博文後,是否明白原因了呢? 是的,上述答案除了D外,全是;
volatile雖然線上程之間變數有可見性,但是卻並沒有保證原子操作;
假如x是一個long或者double型別,且當前系統是32位的,那麼,D選項也需要同步;
java對long和double的賦值操作是非原子操作!!long和double佔用的位元組數都是8,也就是64bits。在32位作業系統上對64位的資料的讀寫要分兩步完成,每一步取32位資料。這樣對double和long的賦值操作就會有問題:如果有兩個執行緒同時寫一個變數記憶體,一個程序寫低32位,而另一個寫高32位,這樣將導致獲取的64位資料是失效的資料。因此需要使用volatile關鍵字來防止此類現象。volatile本身不保證獲取和設定操作的原子性,僅僅保持修改的可見性。但是java的記憶體模型保證宣告為volatile的long和double變數的get和set操作是原子的。
那麼怎樣才能讓全域性變數既方便又安全呢?
請關注:CAS
推薦CAS博文:
https://www.jianshu.com/p/efb2024808a0
http://www.blogjava.net/xylz/archive/2010/07/04/325206.html
推薦volatile博文:
https://www.cnblogs.com/chengxiao/p/6528109.html
https://www.cnblogs.com/zhengbin/p/5654805.html