1. 程式人生 > >多執行緒併發問題以及單例設計模式與執行緒安全以及同步方法和同步程式碼塊

多執行緒併發問題以及單例設計模式與執行緒安全以及同步方法和同步程式碼塊

執行緒安全和非執行緒安全

在作業系統中,執行緒是不擁有資源的,程序擁有資源。執行緒是由程序建立的,一個程序可以建立多個執行緒,這些執行緒共享程序中的資源。當多個執行緒同時操作一個變數時,這個時候就可能會造成資料的不一致性,此時就是執行緒不安全。

JVM有主記憶體(Main Memory)和工作記憶體(Working Memory),主記憶體就是平時所說的java堆記憶體,存放程式中所有的類例項、靜態資料等變數,是執行緒共享的,而工作記憶體中存放的是從主記憶體中拷貝過來的變數以及訪問方法所取得的區域性變數,是每個執行緒獨立所有的,其他執行緒不能訪問。

每個執行緒都有自己的執行空間(即工作記憶體),執行緒執行的時候用到某變數,首先要將變數從主記憶體拷貝的自己的工作記憶體空間,然後對變數進行操作:讀取,修改,賦值等,這些均在工作記憶體完成,操作完成後再將變數寫回主記憶體;

各個執行緒都從主記憶體中獲取資料,執行緒之間資料是不可見的;打個比方:主記憶體變數A原始值為1,執行緒1從主記憶體取出變數A,修改A的值為2,線上程1未將變數A寫回主記憶體的時候,執行緒2拿到變數A的值仍然為1;

多執行緒併發不安全主要就是因為資源的共享,如果沒有資源共享,那麼此時多執行緒併發是安全的。

i++與++i的執行緒安全問題

i++和++i的執行緒安全分為兩種情況:

1、如果i是區域性變數(在方法裡定義的),那麼是執行緒安全的。因為區域性變數是執行緒私有的,別的執行緒訪問不到,其實也可以說沒有執行緒安不安全之說,因為別的執行緒對他造不成影響。

2、如果i是全域性變數(類的成員變數),那麼是執行緒不安全的。因為如果是全域性變數的話,同一程序中的不同執行緒都有可能訪問到。

設計模式

前輩們總結出來的一套合理切高效的設計思路,理解和使用模式,能夠在很大程度上減少時間的浪費以及提高程式碼的可複用性和擴充套件性。

單例設計模式:只有一個例項化物件
單例設計模式適用場景就是:這個類在這個專案中使用頻率十分高,如果每次使用都去new一個物件,此時會造成很大的記憶體開銷。

單例模式分為懶漢式(你需要的時候給你建立)和餓漢式(不管你需不需要我都先建立好)。
單例模式:構造方法都是私有的,避免外界訪問。
懶漢式:有一個私有靜態的成員變數和供外界訪問的一個靜態方法,在靜態方法中進行判斷,如果私有靜態的成員變數是空,那麼還沒有建立物件,開始建立物件,並將其賦值給私有靜態的成員變數。如果不為空,直接返回私有靜態成員變數。
餓漢式:有一個私有靜態的成員變數和供外界訪問的一個靜態方法,私有靜態的成員變數是已經初始化的,即已經建立好物件,在靜態方法中只是將這個建立好的物件返回出去。

使用:
當我們在getInstance()方法中傳參的時候需要使用懶漢式,餓漢式也有一個弊端,就是我們需要獲取一個全域性成員變數的時候,但是得到的卻是一個例項,會導致載入時間過長。

懶漢式是執行緒不安全的,而餓漢式是執行緒安全的。
原因:當多個執行緒併發的情況下執行getInstance()的方法的時候,在懶漢式情況下會出現這種情況:
執行緒一:進入方法getInstance()此時判斷為null,開始進行初始化例項物件;
執行緒二:可能線上程一例項化物件還未建立完成的時候進來,此刻,進行判斷依然為null,然後執行緒二又去例項化物件了,這樣下來不就出現了兩個物件了,顯然這不是想要的結果。

解決辦法:
需要使用synchronied來實現執行緒同步,當一個執行緒獲得鎖的時候其他執行緒就只能在外等著 ,在getInstance()方法上加鎖。但是也有弊端:會大大降低效能,同步方法要比一般方法慢很多,如果頻繁的呼叫getInstance()方法,會造成效能的累計消耗。

public synchronized static Demo getDemo(){
        if(d==null){
            d=new Demo();       
        }
        return d;
    }

使用雙重校驗鎖:
在進行判斷當前物件是否為空的時候,使用同步程式碼塊。這個時候,但是當執行緒1和執行緒2同時判斷(d==null)的時候就會一起進入到同步程式碼塊當中。此時在同步程式碼塊中要在進行判斷是否為空,然後為空再建立物件。不然還是會創建出來兩個物件。還要使用關鍵字volatile來保證禁止指令重排優化了。

public class Demo {
    private static volatile Demo d;

    private Demo(){};

    public static Demo getDemo(){
        if(d==null){
            synchronized(Demo.class){
                if(d==null){
                    d=new Demo();       
                }               
            }           
        }
        return d;
    }
}

同步方法

所有非靜態同步方法使用的都是同一把鎖,——->例項物件本身,即this
所有的靜態同步方法使用的也是同一把鎖,——–>類物件本身,即class.
非靜態同步方法和靜態同步方法使用的是兩個不同的鎖物件,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。
對於同步塊,由於其鎖是可以選擇的,所以只有使用同一把鎖的同步塊之間才有著競態條件,

同步的例子