1. 程式人生 > >從單例模式說起

從單例模式說起

等待 有一個 new null 效率 知識 HR 成了 .get

單例模式是我們比較常用的設計模式,玩好單例模式也會涉及到很多java基礎知識。
單例作為全局性實例,在多線程情況下全局共享的變量會變得非常危險。

雙重檢測:

雙重檢測是比較常用的一種實現方式:

public class Singleton {
    public static final volatile Singleton singleton = null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(singleton == null){ 
           synchronize (Singleton.class){
               if( singleton == null ) { 
                   singleton = new Singleton();
               }
        }
        return singleton;
    }
}

如果不用volatile修飾,多線程執行到 singleton == null 時,多個實例會被創建出來,就可能造成內存泄露問題。

當然你可以說可以用互斥同步的方式進行,但是我們做了同步,多線程的操作就變成了串型了,效率會很低,因為創建對象其實只需要一次,但是後面的讀取都需要同步了。

還有一個原因,在jvm編譯器可能會對指令進行重拍和優化,就是判斷singleton == null的判斷順序可能無法保證。
於是我們將變量用volatile修飾,這個變量就不會在多線程中存在副本,都必須從主內存讀取,同時避免了指令重拍。

當兩個線程執行完第一個 singleton == null 後等待鎖, 其中一個線程獲得鎖並進入synchronize後,實例化了,然後退出釋放鎖,另外一個線程獲得鎖,進入又想實例化,會判斷是否進行實例化了,如果存在,就不進行實例化了。

靜態內部類(懶漢模式)

一個延遲實例化的內部類的單例模式,一個內部類的容器,調用getInstance時,JVM加載這個類

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

由於SingleHolder是私有的,除了getInstance()之外沒有方法可以訪問它,只有在getInstance()被調用時才會真正創建,

首先,其他類在引用這個Singleton的類時,只是新建了一個引用,並沒有開辟一個的堆空間存放(對象所在的內存空間)。
接著,當使用Singleton.getInstance()方法後,Java虛擬機(JVM)會加載SingletonHolder.class(JLS規定每個class對象只能被初始化一次),並實例化一個Singleton對象。

缺點:

需要在Java的另外一個內存空間(Java PermGen 永久代內存,這塊內存是虛擬機加載class文件存放的位置)占用一個大塊的空間。


從單例模式說起