Java並發-volatile的原理及用法


Java並發-volatile的原理及用法


 

volatile屬性:可見性、保證有序性、不保證原子性。
一、volatile可見性
  在Java的內存中所有的變量都存在主內存中,每個線程有單獨CPU緩存內存,多個線程對同一個變量讀取時,會從主內存中把變量拷貝到自己的CPU緩存中,線程之間也無法直接訪問對方CPU緩存內存中的變量,只能通過主內存傳遞變量的值;
  舉個例子、例一;

1 int i=0;
2 //線程一中執行
3 i=1;
4 //線程二中執行
5 int j=0;
6 j=i;

  上面這個程序在線程一中,讀取內存變量i的值到線程一的CPU緩存中,線程一的CPU緩存中並將i的值設為1,這時還沒來的及將i的值從線程一的CPU緩存中寫入到主內存中時,此時線程二從主內存中讀取到i的值到線程二的CPU緩存中,此時i還是0,線程二並沒有及時得到線程一修改的值,這就是不可見性;
  如果把上面程序的int i=0換成volatile int i=0,就可以保證線程二可以立刻可見線程一修改i后的值,因為JVM 保證了每次讀變量都從內存中讀,相當於跳過 CPU cache 這一步,讓線程一直接修改主內存中i的值;
  但是如果將i換成volatile int i,將i=1換成i++,線程二對線程一的改變i后的值仍是不可見的,原因就是下面要說的原子性;
二、volatile不可保證原子性
  原子性就是指操作不可分割,比如i=1這個就不可分割,i++就包含三個操作:讀取i的值,進行加1操作,寫入新的值,這就可分割不是原子操作。如果將i換成volatile int i,將i=1換成i++,當線程一執行到對i加1操作時,還沒有來得及寫入新值時,線程二執行j=i,線程獲取的i值任然為0;可以通過synchronized和Lock來實現更大范圍操作的原子性。
三、volatile保證有序性
有序性,及程序按照編寫的順序先后執行。
  舉個例子、例二:

1 int i=0;//1
2 int j=1;//2
3 i++;//3
4 j++;//4

  上面這個程序按照正常邏輯執行順序應該是1>2>3>4;但是在JVM中並一定不會按照這個順序執行,因為處理器為了提高運行效率,在JVM中的及時編譯存在指令重排序的優化,它會改變各個語句的執行順序,但是不改變運行結果,就是改變后的順序運行結果和改變前的結果一樣,就行如果按照1>3>2>4,雖然改變順序,但並不影響結果;
如果把程序改變成這個樣子、例三:

1 int i=0;//1
2 int j=1;//2
3 i++;//3
4 j=i+1;//4

  上面這個程序執行順序一定是1、2在3前面,3一定會在4前面,1和2順序可變,因為處理器在進行重排序時是會考慮指令之間的數據依賴性,如果一個指令指令2必須用到指令1的結果,那么處理器會保證指令1會在指令2之前執行。
重排序雖然對單線程沒影響但是對多線程就會產生影響,使結果不一樣;
舉個例子、例四:

1 User user;//1
2 boolean flag=flase;//2
3 //線程一
4 user=new User();//3
5 flag=true;//4
6 //線程二
7 if(flag){
8 user.name="張振力";//5
9 }

  上面這個程序的意思就是,如果在線程一user完成初始化,就把flag設置為true,在線程二中如果flag為true,就初始化user.name的值;但是在實際中因為3和4並沒有依賴,會被重排序,語句4先執行,語句3還沒執行,在線程二中得到user值就會為空,這時候給name賦值就會報錯。
  通過定義volatile就可以保證語句3一定在語句4前執行,就是因為volatile定義的變量在賦值時,在其上面的程序是一定完成過的,在其后的程序一定是沒完成的,而且其前完成的操作對其后的是可見的;舉個例子、例五:

1 volatile boolean flag =false;
2 int i,j,x,y;
3 i=10;//1
4 j=10;//2
5 flag=true;//3
6 x=10;//4
7 y=10;//5

  上面的例子執行的順序就是在3之前1和2語句已經執行完(不保證1、2的順序),但4和5語句(不保證4、5的順序)肯定還沒執行;
  再返回例四里面看,如果flag定義為volatile,那肯定能保證語句3一定在語句4前執行,如果flag為true,那user肯定完成了初始化,就不會報錯。

1 User user;//1
2 volatile boolean flag=flase;//2
3 //線程一
4 user=new User();//3
5 flag=true;//4
6 //線程二
7 if(flag){
8 user.name="張振力";//5
9 }

四、volatile實現可見性和有序性的原理: 

1.可見性

  如果對聲明了volatile變量進行寫操作時,JVM會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫會到系統內存。 這一步確保了如果有其他線程對聲明了volatile變量進行修改,則立即更新主內存中數據。

2.有序性

  但這時候其他處理器的緩存還是舊的,所以在多處理器環境下,為了保證各個處理器緩存一致,每個處理會通過嗅探在總線上傳播的數據來檢查 自己的緩存是否過期,當處理器發現自己緩存行對應的內存地址被修改了,就會將當前處理器的緩存行設置成無效狀態,當處理器要對這個數據進行修改操作時,會強制重新從系統內存把數據讀到處理器緩存里。 這一步確保了其他線程獲得的聲明了volatile變量都是從主內存中獲取最新的。

  在這里就講這一個使用場景:狀態標記,后續會再寫另一個使用場景,單例模式下的volatile確保單例對象的返回的正確性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM