1. 程式人生 > >【劍指offer Java】面試題2:實現Singleton模式

【劍指offer Java】面試題2:實現Singleton模式

題目:設計一個類,我們只能生成該類的一個例項。

//餓漢式
public static class Singleton01{
        //預先初始化static變數
        private final static Singleton01 INSTANCE = new Singleton01();

        private Singleton01(){

        }

        public static Singleton01 getInstance(){
            return INSTANCE;
        }
    }

1.優點:執行緒安全,因為static類在類載入時只初始化一次,保證執行緒安全

2.缺點:一來就建立例項化物件,不管後面這個物件用不用的到,所以缺點是若構造的例項很大,而構造完又不用,會導致資源的浪費

//懶漢式,無同步鎖
public static class Singleton02{
        private static Singleton02 instance = null;
        //私有化構造方法,保證外部類不能通過此構造器來例項化
        private Singleton02(){

        }

        //當多執行緒同時呼叫getInstance()時,可能創造多個例項物件,並且將多個例項物件賦值給例項變數instance
public static Singleton02 getInstance(){ if(instance == null) instance = new Singleton02(); return instance; } }
  1. 優點:懶漢式只有在用到的時候(即呼叫getInstance()方法)才建立例項化物件,避免資源浪費
  2. 缺點:1、若初始化非常耗時時,會造成資源浪費,2、非執行緒安全,多執行緒可能趙成多個例項物件被初始化
//懶漢式,同步鎖
public static
class Singleton03{ private static Singleton03 instance = null; private Singleton03(){ } /* * 獲得單例物件例項,同步互斥訪問實現執行緒安全 */ public static synchronized Singleton03 getInstance(){ if(instance == null) instance = new Singleton03(); return instance; } }

優點:1、當要使用時才例項化單例,避免資源浪費。2、執行緒安全
缺點:1、若初始化例項時耗時,則會造成效能降低。2、每次呼叫getInstance()都獲得同步鎖,效能消耗(但卻無法避免)

 針對Singleton03出現的每次呼叫getInstance()都獲得同步鎖而出現的效能問題,那麼將synchronized關鍵字放到getInstance()呼叫函式裡面,如下面Singleton04()程式碼所示。
 但隨之而來的問題是:多執行緒下同時呼叫getInstance(),這時instance都為空,多執行緒都進入了synchronized塊建立例項,就和前面的Singleton03一樣了
//懶漢式,試圖將同步鎖放在判斷是否為單例(instance == null)之後
public static class Singleton04{
        private static Singleton04 instance = null;
        private Singleton04(){

        }

        public static Singleton04 getInstance(){
            if(instance == null){
                synchronized (Singleton04.class){
                    instance = new Singleton04();
                }
            }
            return instance;
        }
    }
  為了解決Singleton03()、Singleton04()帶來的可能建立多例項物件問題,所以用雙重校驗鎖。
  之所以有兩個(instance == null)判斷是因為考慮了多執行緒問題(這也是所謂的雙重校驗鎖),當有多個執行緒同時呼叫getInstance()的時候,這些執行緒都能進入第一個if(instance == null)判斷,然而只有一個執行緒能進入第二個if(instance == null)來建立物件。所以,若沒有第二個(instance == null)判斷,那麼多個執行緒將會建立多個物件。如Singleton05()所示:
//懶漢式,雙重校驗鎖
public static class Singleton05{
        private volatile static Singleton05 instance = null;
        private Singleton05(){

        }
        public static Singleton05 getInstance(){
            if(instance == null){
                synchronized (Singleton05.class){
                    if(instance == null)
                        instance = new Singleton05();
                }
            }
            return instance;
        }
    }
   我們知道,JVM的記憶體模型中有一個“無序寫”(out-of-order writes)機制,而雙重校驗鎖中instance = new Singleton05()中做了兩件事: 
      1、呼叫構造方法,建立例項化物件。  
      2、將例項化物件賦值給引用變數instance

   因為“無序寫”機制,所以步驟1、2可能是亂序的,因此Singleton05中多執行緒可能因為第一個執行緒instance = new Singleton05()先賦值了instance,但是例項化物件卻沒有建立因此下一個執行緒進入(instance == null)判斷時為false,因此直接返回了instance。但是這個instance是沒有經過例項化物件賦值而是預設值的,所以是錯誤的。而這時第一個執行緒才開始例項化物件,並且把正確的例項化物件賦值給引用instance。 為了解決JVM的“無序寫”問題,用臨時變數tmp 

PS:其實就是兩次(instance == null)判斷後面需要synchronized同步鎖來進行執行緒互斥

//懶漢式,雙重校驗鎖
public static class Singleton06{
        private static Singleton06 instance = null;
        private Singleton06(){

        }
        public static Singleton06 getInstance(){
            if(instance == null){
                synchronized(Singleton06.class){
                    Singleton06 tmp = instance;
                    if(tmp == null){
                        synchronized(Singleton06.class){
                            tmp = new Singleton06();
                        }
                        instance = tmp;
                    }
                }
            }
            return instance;
        }
    }
//靜態方法塊只調用一次,省去了if(instance == null)的比較

    public static class Singleton07{
        private static Singleton07 instance = null;
        static{
            instance = new Singleton07();
        }
        private Singleton07(){

        }

        public static Singleton07 getInstance(){
            return instance;
        }
    }
  使用靜態內部類SingletonHolder來實現懶漢式單例,因為JVM中內部類只有在getInstance()方法第一次被呼叫時才被載入,即實現了懶漢式(lazy).而static內部類只會被載入一次,因此載入過程是執行緒安全的
//懶漢式,靜態內部類實現
public static class Singleton08{
        private final static class SingletonHolder{
            private static final Singleton08 INSTANCE = new Singleton08();
        }
        private Singleton08(){

        }

        public static Singleton08 getInstance(){
            return SingletonHolder.INSTANCE;
        }
    }
/*
 * 總的來說,就是要考慮是否是懶漢式單例?是否執行緒安全?是否效能最好?
 * 按照程式碼的邏輯來考慮,就是是否是懶漢式單例【即(instance == null)】?
 * 然後考慮是否執行緒安全【即synchronized的使用】?
 * 最後考慮效能如何【即sychronized關鍵字使用是在getInstance()還是在
 * (instance == null)之中】
 */