1. 程式人生 > >Java記憶體模型 之三個特性:

Java記憶體模型 之三個特性:

Java記憶體模型有三個特性:原子性、可見性、有序性。

這個三個特性主要體現在多執行緒環境下對變數的操作。這些變數包括:例項欄位、靜態欄位、構成陣列物件的元素。這些變數都儲存在堆中,
堆是執行緒共享的。那麼這些變數在多執行緒環境下就有可能出現所謂“執行緒不安全”的問題。 另,區域性變數和方法引數是執行緒私有的,儲存在棧中,不會出現執行緒安全問題。

原子性:
這個概念是事務中的原子性大概一致。表明此操作是不可分割的,不可中斷,要全部執行,要麼全部不執行。這個特性的重點在於不可中斷,舉例子說明下,如下程式碼:

int  a=0;
int  b=0;
int  c=0;

執行緒A執行上述程式碼,從記憶體中讀取這三個變數的值,在讀取的過程中,此時執行緒B也讀取了某一個變數的值,此時雖然執行緒B的這個操作並不會對執行緒A的結果產生影響,但是執行緒A的原子性已經不存在了,在底層CPU執行的時候,就會涉及到切換執行緒A、B。並且,對A要進行中斷,所以執行緒A的原子性就被破壞了。理解這一點,也就會理解關鍵字volatile並不能保證原子性,保證原子性需要加鎖。

在單例模式中,如果不是使用加鎖的方法,就會因為沒有保證原子性,而使得物件會被建立多個。

package com.txc.singleton;

import java.io.Serializable;

public class Singleton1 {


    private volatile static Singleton1 singleton=null;


    private  static boolean  flag = true;
    private Singleton1 (){   }

    public static Singleton1 getSingleton
() { if (singleton == null) { singleton = new Singleton1(); } return singleton; } }

雖然使用了關鍵字volatile,但是兩個執行緒會同時執行getSingleton()方法,並且可以都通過為null的判斷,然後進行物件的建立,因為getSingleton()這段程式碼的原子性沒有被保證,保證的方式就是為這個方法加鎖。

還有一個例子就是32bit的jvm操作long型別資料。Long需要64位,多執行緒環境下,對long型別的資料進行加運算,有可能一個執行緒讀取的資料是另一個執行緒修改“一半”的資料,比如只修改了低位,還沒有修改高位,此時資料被另一個執行緒讀取,那麼結果自然就是錯的。解決辦法就是在修改long資料的方法上加鎖,保證此方法被某一執行緒呼叫時,不被其他執行緒干擾。
可見性:


一個執行緒對某一共享變數修改之後,另一個執行緒要立即獲取到修改後的結果。那麼會不會出現一個執行緒沒有及時獲取到另一個執行緒修改變數後的情況呢?當然有,看單例模式。

package com.txc.singleton;

import java.io.Serializable;

public class Singleton1 {



    private static Singleton1 singleton;


    private  static boolean  flag = true;
    private Singleton1 (){

    }

    public static Singleton1 getSingleton() {
        if (singleton == null) {//程式碼x
            synchronized (Singleton1.class) {
                if (singleton == null) {
                    singleton = new Singleton1();
                }
            }
        }
        return singleton;
    }


}

上述單例模式,單例物件singleton,沒有使用volatile關鍵字,那麼兩個執行緒同時執行到程式碼x時候,此時只有一個執行緒獲的鎖,然後建立singleton例項,此時singleton的值(引用),儲存在私有記憶體中,並不會馬上重新整理進主記憶體,而另一個執行緒使用的singleton的值,也是這個執行緒私有記憶體的值,並不一定會讀取主記憶體中的singleton,也會執行後面的程式碼再進行建立物件。上述情況就是記憶體中一個變數的值,存在了不可見性,即另一個執行緒沒有馬上得到最新的變數的值。當然使用volatile可以保證可見性。

執行緒在使用共享變數時,首先將變數從主記憶體中load進私有記憶體,然後在私有記憶體中修改,修改完之後在save進主記憶體。可見性就是指此load和save操作應立即執行。保證可以立即執行的方法有使用volatie關鍵字。在單例模式的雙重校檢中有用到。Volatie的作用就是保證執行緒在使用共享變數時一定從主記憶體中load進私有記憶體,而不是直接使用私有記憶體中儲存的副本。在修改完之後立即將此變數save進主記憶體。
有序性:
在單執行緒環境下,程式永遠會“有序的”執行,即:執行緒內表現為序列語義。但是在多執行緒環境下,程式會出現“亂序”。這是由於指令重排和主記憶體和私有記憶體同步延遲造成的。在Java中使用volatile和synchronized來保證多執行緒之間的有序性。Volatile通過加入記憶體屏障指令來禁止記憶體的重排序。Synchronized通過加鎖,讓一段程式碼只能由一個執行緒來執行。這兩個區別就是,synchronized可以修飾一段程式碼,或者一個方法。但是volatile只能修飾一個變數。