1. 程式人生 > >java併發:volatile關鍵字

java併發:volatile關鍵字

計算機快取一致性

每核cpu在執行指令時,都會將資料從主存中讀取,然後在自身的快取記憶體中操作修改,最後重新整理到主存中.

但有個問題,就是重新整理時間不確定.如果此時主存有個變數

i = 0;

兩個cpu執行緒同時執行下面的程式碼;

i++;

 最終主存的這個i 卻不一定是 2;

這就是快取一致性的問題,有兩個解決方案:

1.匯流排鎖機制.操作共享資源都加鎖.好吧,這效率就甭說了.

2.快取一致性協議,最出名的是Intel的MESI,意思是說,當cpu寫完資料後,會通知其他cpu,去檢視其快取記憶體中是否有該變數的副本,如果有的話,請設定為無效,然後重新去主存中讀取.

原子性/可見性/有序性

原子性:經典的例子就是轉賬,不能中斷.

可見性:共享的變數某執行緒修改後,立即重新整理主存,那麼這個改變對其他執行緒來說,便是可見的.

有序性:處理器出於優化的機制會對程式碼指令重排,但並不影響結果.

比如

int a = 1 ;//指令1
int b = 2 ;//指令2
int c = a + b;//指令3

那麼處理器真正執行的

可能是:指令1 ---指令2 ----指令3,

也可能是:指令2---指令1---指令3

但絕不會先執行指令3 ,因為指令3具有依賴性.

這對於單執行緒來說,沒什麼問題.可是對於多執行緒來說呢?

博主也不清楚.

JAVA記憶體模型

java有自己的一套記憶體模型,並定義了執行順序,但是出於效率考慮,java並沒有限制物理機的一些提升操作.也即是java也存在著快取一致性和指令重排序問題.

java記憶體模型規定:所有變數存在於虛擬機器內的主存(類似於機器主存),每條執行緒都有一個工作記憶體(類似於cpu快取記憶體).變數的更新都必須在工作記憶體中執行,然後重新整理到主存.不能直接在主存中更新.

JAVA如何保證原子性:

java只保證基本資料型別的讀取和賦值是原子性的.

int a = 1;//賦值10給a
int b = a;//讀取a,賦值a給b
a ++;//讀取a,賦值a+1給a

以上三個只有第一行是原子性的.其他都不是.

當然synchronized和Lock是保證程式碼片的執行時原子性的.

JAVA如何保證可見性:

volatile可以保證可見性,被volatile修飾的共享變數修改後會立即重新整理到主存.

當然synchronized和Lock算是隻允許單執行緒操作,對單執行緒來說,討論可見性是沒有意義的.

JAVA如何保證有序性:

volatile也可以保證有序性.在後面說.

當然synchronized和Lock算是隻允許單執行緒操作,對單執行緒來說,討論有序性是沒有意義的.因為不影響結果.

實質上,java內部就對執行順序有個規則,叫做happens-before原則(先行發生原則):

  • 程式次序規則:一個執行緒內,按照程式碼順序,書寫在前面的操作先行發生於書寫在後面的操作
  • 鎖定規則:一個unLock操作先行發生於後面對同一個鎖額lock操作
  • volatile變數規則:對一個變數的寫操作先行發生於後面對這個變數的讀操作
  • 傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C
  • 執行緒啟動規則:Thread物件的start()方法先行發生於此執行緒的每個一個動作
  • 執行緒中斷規則:對執行緒interrupt()方法的呼叫先行發生於被中斷執行緒的程式碼檢測到中斷事件的發生
  • 執行緒終結規則:執行緒中所有的操作都先行發生於執行緒的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到執行緒已經終止執行
  • 物件終結規則:一個物件的初始化完成先行發生於他的finalize()方法的開始

解讀部分:

規則1:幾乎是所有程式設計的規則.自上而下從左至右.但依然有可能指令重排.

規則2:解鎖發生在另一個執行緒的開鎖之前.

規則3:對一個變數的寫操作先行發生於後面對這個變數的讀操作.非常精簡.這個在後面會說.

真volatile

volatile的作用:

1.保證了不同執行緒對這個變數進行操作時的可見性,一個執行緒修改後,其他執行緒都知道.

2.禁止指令重排.

第一條它保證了可見性.

第二條的意思:

當執行到volatile時,前面的程式碼必須都執行完了.volatile後面的程式碼必須都未執行.不管是否重排.

int a = 1;
int b = 2;
boolean flag = true;//volatile
int c = 3;
int d = 4;

這段程式碼.執行到flag時,c和d都沒有執行.

而且指令重排時,a和b可以互換,c和d可以互換,但是無法越界.a和c無法互換.

volatile就是一道柵欄.它可以保證一定的有序性.

但是不能保證原子性.

為什麼?

假如兩條執行緒都去操作下面程式碼片:

a++;

那麼a++本身是 讀取和更新兩個步驟.

volatile可以保證讀取時在更新之前,但不能保證讀取是在讀取之前.

比如執行緒1讀取了a,此時在更新狀態.那麼執行緒2就不能讀取,因為執行緒1還沒有更新完到主存.

但是如果執行緒1讀取了a,還沒有更新,那麼執行緒2就可以讀取,