Java雙重檢查式單漢式單例模式&存在的問題&volatile解決

在Java設計模式之單漢式單例模式涉及到多線程訪問時,可能用到雙重檢查式。代碼如下:

package com.charles.singleton
public class Singleton
{
    private static Singleton instance=null;
    private Singleton(){}
    public static Singleton getInstance()
    {   
        if(instance==null)  //point 1
        {
            synchronized(Singleton.class){
            if(instance==null)  //point 2
            {
                instance=new Singleton();  //May come into Exception
            }
            }
        }
        return instance;
    }
}

以上代碼使得當多個線程同時獲取該類對象時,只能持有一個同一個對象的引用。假設同時有線程A以及線程B同時訪問,當線程A進入實例之後,進入point 1,此時instance假設爲null,滿足條件,加鎖進入point 2,此時依然滿足條件,則實例化instance對象,並返回該對象實例。線程B此時訪問,則不滿足point 1,直接返回instance對象實例,不會第二次實例化對象。可以看出,雙重檢查可以使得其執行效率變高,在point 1處,並不會觸發synchronized,就不會產生線程的排隊等待問題,提高了效率。

其實,這種訪問模式是有一定的問題存在,因爲在JVM中,存在重排序的問題。在極端情況下,可能會因爲重排序問題而導致線程之間的不安全。

重排序:

重排序是JVM中的一種優化代碼的運行機制,其特點是語句的原子性。而上述可能引起問題的代碼就在

instance=new Singleton();

這句。因爲其並非原子性操作,可能會由於重排序(不會引發單線程執行結果的改變)而引起問題。在JVM中,這句話會被分成三步執行,如下:

1.JVM爲其分配內存地址以及內存空間

2.使用構造方法實例化對象

3.將分配的內存地址賦予對象

JVM在執行時,如果重排序可能會有以下幾種可能:

  執行順序:1 、2 、3

執行完畢,不出問題。

  執行順序:1 、 3 、2

執行完畢,產生問題。

產生問題原因,假設線程A剛執行了1,分配了地址以及空間,此時正常。執行3,將分配的地址給對象,正常。此時還未執行2,未實例化該對象,但此時另一個線程B已經到來,由於線程A已經爲instance配了地址,instance不爲null(但是並沒有用構造方法實例化對象,即new Singleton()),返回instance。那麼現在線程B拿到的單例對象就不能使用(沒有實例化),而產生錯誤。

解決問題方法:

可以看出,產生這種錯誤的原因是因爲JVM的重排序導致,那麼我們可以使用一個關鍵字來禁止重排序即volatile。

private volatile static Singleton instance=null;

這樣,JVM在執行時便不會對其進行重排序而產生錯誤。

解決問題的原理(volatile):

volatile是通過內存屏障來防止重排序問題

1。在volatile寫操作前,插入StoreStore屏障

2。在volatile寫操作後,插入StoreLoad屏障

3。在volatile讀操作前,插入LoadLoad屏障

4。在volatile讀操作後,插入LoadStore屏障

 

 

 

 

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