JAVA基礎-volatile關鍵字

首先我們知道在java中內存的交互有以下幾點。
 
Read Load 和 Store Write 兩對操作不可分割。
 
Lock(鎖定):作用於主內存的變量,他把變量標識爲一個線程獨佔的狀態。
UnLock(解鎖) :作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定。
Read(讀取):作用於主內存的變量,它把一個變量的值從主內存中讀取到線程工作內存中,以便隨後的load動作使用。
Load(載入):作用於工作內存中的變量,它把read操作從主內存中得到的變量值放入工作內存中的變量副本中。
Use(使用):作用於工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值的字節碼指令將會執行這個操作。
Assign(賦值):作用於工作內存的變量,它把執行引擎接收到的值付給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
Store(存儲):作用於工作內存的變量,它把工作內存中的一個變量的值傳遞到主內存中,以便隨後的write操作使用。
Write(寫入):作用於主內存的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中。

Java內存模型還規定了在執行上述8種基本操作必須滿足如下規則:
  1. 不允許read和load。store和write操作之一單獨出現。
  2. 不允許一個線程丟棄它最近的assign操作,即變量在工作內存中改變了之後必須把該變化同步回主內存。
  3. 不允許一個線程無原因的把數據從線程的工作內存同步回主內存。
  4. 一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用了一個未被初始化的變量,換句話說,就是一個變量實施use、store操作之前,必須先執行過了assign和load操作。
  5. 一個變量在同一個時刻只允許一個線程對其進行lock操作,但lock操作可以被同一條操作重複執行多次,多次執行lock之後,只有執行相同次數的unlock操作,變量纔會被解鎖。
  6. 如果對一個變量執行lock操作,那將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新load和assign操作初始化變量的值。
  7. 如果一個變量實現沒有被lock操作鎖定,那就不允許對它執行unlock操作,也不允許去lock一個被其他線程鎖定住的變量。
  8. 對一個變量執行unlock操作之前,必須先把此變量同步回主內存中。
 
如果要把一個變量從主內存複製到工作內存,就要順序地執行read和load操作,如果把變量從工作內存同步回主內存,就要順序地執行store和write操作。
 

 
volatile的作用是,能夠保證變量對線程的可見性,也就是說,變量值被其中一個線程更改後對其他線程是可見的,但是並不能保證複合操作的原子性(例如i++)。所以要想保證線程安全,還需要通過其他的方式,例如synchronized關鍵字或者Lock鎖。
如果需要對基本類型進行原子操作,可以使用基本類型的Atomic封裝類。例如AtomicInteger,裏面封裝了一系列保證原子性的操作方法。   
 
volatile是如何保證變量對其他線程的可見性?
1、每次對變量的讀操作都從主內存中獲取。
2、每次對變量的寫操作都第一時間寫到主內存中。
並且volatile禁止指令的重排序,對在讀之前和寫之後加入一個內存屏障這樣可以保證:一個線程寫入一個變量後,任何線程都會得到最新值
 
happens-before中的一條
volatile域規則:對一個volatile域的寫操作,happens-before於任意線程後續對這個volatile域的讀。
 
可以通過一段代碼來證明volatile是非線程安全的。
 
 
/**
* Created by kaijiyu on 2018/3/27.
*/
public class VolatileDemo {
    static volatile int x = 0;
    public static void add(){
        x++;
    }
 
    public static void main(String[] args) {
 
        for (int i = 0; i < 10; i++) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        add();
                    }
                }
            });
            thread1.start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(x);
    }
 
}
這段代碼每次執行的結果都是不一樣的,如果能夠保證線程安全的話,x的值應該始終爲100000;
如果要保證線程安全,只需要給add()方法加一個synchronized關鍵字即可。
 
volatile關鍵字有時候會被錯誤的理解,認爲只要對變量使用volatile關鍵字就會確保線程安全,其實並不是,volatile只能起到一個簡單的同步作用。
 
只有在以下兩種情況下,纔可以保證原子性。
1、運算結果不依賴變量的當前值,或者只有單一線程修改變量值
2、變量不需要和其他狀態變量共同參與不變約束
 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章