1. 程式人生 > >【設計模式】——單例模式的幾種寫法

【設計模式】——單例模式的幾種寫法

單例模式:保證一個類僅有一個例項,並提供一個訪問他的全域性點。

懶漢式,執行緒不安全

//懶漢式,執行緒不安全
public class Sington {
    private Sington(){};//讓外界不能通過new來建立例項
    private static Sington instance;

    public static Sington getInstance(){  //如果例項存在則直接返回,如果沒有存在建立例項返回
        if(instance==null){
            instance=new Sington();
        }
        return instance;
    }

    public static  void main(String[] args){
        Sington s1=Sington.getInstance();
        Sington s2=Sington.getInstance();

        if(s1==s2){
            System.out.println("相同例項");
        }else {
            System.out.println("不是相同例項");
        }

    }
}

上述例子如果實在併發情況下,就不能實現單例的效果。比如執行緒A在判斷instance為空時,進入new操作,但是操作還沒有完成,此時執行緒B剛剛好也執行到判斷instance是否為null,那麼就可能造成執行緒B也會就new一個例項。這樣就違背了單例原本的含義。

第一步優化:為了解決多執行緒的同步問題我們可以使用synchronized關鍵字。

 public static synchronized Sington getInstance(){  //如果例項存在則直接返回,如果沒有存在建立例項返回
        if(instance==null){
            instance=new Sington();
        }
        return instance;
    }

使用synchronized關鍵字可以保證單例,但是這時候就會有效能的問題,因為getInstance整個方法都是執行緒同步的,這樣就限定了訪問的速度。這時候我們進行第二次優化

雙重檢驗索  執行緒安全

public class Singleton {
    private static Singleton instance; //宣告靜態的單例物件的變數
    private Singleton(){};//私有構造方法

    public static Singleton getInstance(){//外部通過此方法可以獲取物件
        if(instance==null){
            synchronized (Singleton.class){//保證了同一時間只能只能有一個物件訪問此同步塊
                if(instance==null){
                    instance=new Singleton();
                }
            }
        }
        return instance;//返回建立好的物件   
    }
}

雙重檢驗鎖模式:是一種使用同步塊加鎖的方法。因為有兩次檢查,一次在同步塊外,一次在同步塊內。為什麼同步塊內還要檢驗一次呢?因為可能會有多個執行緒一起進入同步塊外的if,如果在同步塊內不進行就可能生成多個例項了。這段程式碼看起來沒有問題,其實不然。主要在於instance=new Singleton(),這並非一個原子操作,實際上在

JVM中大概會做三件事。

1、給instance分配記憶體

2、呼叫Singleton的建構函式來初始化成員變數

3、將instance物件指向分配的記憶體空間。(執行完這步instance就是非null了)

但是JVM在即時編譯器中存在指令重排序的優化。也就是說上面的第2、3步順序是不能保證的,執行的順序可能是1-2-3或者1-3-2。如果是後者,則在3執行完畢、2未執行之前,被執行緒二搶佔了,這是instance已經是非null了(但是卻沒有初始化),所以執行緒二就會直接返回instance,然後使用,就會報錯

解決方法:只需要將instance宣告稱volatile就可以了。使用原因:volatile是禁止指令重排序優化。也就是說在volatile變數的賦值操作後面會有一個記憶體屏障(生成的彙編程式碼上),讀操作不會重排序到記憶體屏障之前

餓漢式 執行緒安全

public class Singleton{
    //類載入時就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}
 
    public static Singleton getInstance(){
        return instance;
    }
}

這種方法非常簡單,因為單例的例項物件被宣告成了static和final變數,在第一次載入類到記憶體時就會進行初始化,所以建立例項本身是執行緒安全的。但是這個模式也有缺點,就是不管是否呼叫例項,都已經載入到了記憶體中。

靜態內部類  執行緒安全

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

這種寫法仍然使用JVM本身機制保證了執行緒安全的問題,由於SingletonHolder是私有的,除了本類中的getInstance方法沒有辦法訪問他,所以他其實是懶漢式的。而在讀取的例項的時候不會進行同步,又沒有效能缺陷。也不依賴於jdk的版本,所以建議使用。