天天看點

劍指offer 面試題2之單例模式實作

在應聘比較知名的IT或者網際網路企業的時候,不管是進階還是中初級工程師都會有很大幾率遇到筆試,筆試内容主要就是考察基礎了,而且在面試的時候也經常會讓手寫算法的時候,這部分内容的準備可以刷下劍指offer和leetcode,我這邊主要是java,之前看的劍指offer主要是用c++實作的,這邊就用java把劍指offer裡面的面試題大部分都實作一下。

面試題二:實作單例模式

不推薦的解法一,單線程模式下的單例模式,在多線程下并不是線程安全的。

package partOne;

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

}
           

解法二 餓漢式(靜态常量)采用final static 關鍵字

package partOne;

public class SingletonOne {
    private final static SingletonOne singletonOne = new SingletonOne();
    private SingletonOne(){}
    public SingletonOne getInstance(){
        return singletonOne;
    }
}
           

這種寫法優點是保證了線程安全,當由于在類加載的時候就已經将單例載入了,是以如果單例沒有調用的話會浪費記憶體空間

解法三 餓漢式(靜态代碼塊)

package partOne;

public class SingletonTwo {
    private static SingletonTwo singletonTwo;
    static {
        singletonTwo = new SingletonTwo();
    }
    private SingletonTwo(){}
    public SingletonTwo getInstance(){
        return singletonTwo;
    }
}
           

這種方式和上面的方式其實類似,隻不過将類執行個體化的過程放在了靜态代碼塊中,也是在類裝載的時候,就執行靜态代碼塊中的代碼,初始化類的執行個體。優缺點和上面是一樣的。

解法四 懶漢式(線程安全,同步方法)

package partOne;

public class SingletonThree {
    private static SingletonThree singletonThree = null;
    private SingletonThree(){}
    public static synchronized SingletonThree getInstance(){
        if(singletonThree==null){
            singletonThree = new SingletonThree();
        }
        return singletonThree;
    }


}
           

解決上面第三種實作方式的線程不安全問題,做個線程同步就可以了,于是就對getInstance()方法進行了線程同步。

缺點:效率太低了,每個線程在想獲得類的執行個體時候,執行getInstance()方法都要進行同步。而其實這個方法隻執行一次執行個體化代碼就夠了,後面的想獲得該類執行個體,直接return就行了。方法進行同步效率太低要改進

解法五 雙重檢查鎖(double check)

package partOne;

public class SingletonFour {
    private static SingletonFour singletonFour = null;
    private SingletonFour(){}
    public static SingletonFour getInstance(){
        if(singletonFour == null){
            synchronized (SingletonFour.class){
                if(singletonFour == null){
                    singletonFour = new SingletonFour();
                }
            }
        }
        return singletonFour;
    }
}
           

Double-Check概念對于多線程開發者來說不會陌生,如代碼中所示,我們進行了兩次if (singleton == null)檢查,這樣就可以保證線程安全了。這樣,執行個體化代碼隻用執行一次,後面再次通路時,判斷if (singleton == null),直接return執行個體化對象。

優點:線程安全;延遲加載;效率較高。

解法六 内部靜态類

package partOne;

public class SingletonFive {

    private SingletonFive(){}
    private static class Singleton{
        private static final SingletonFive INSTANCE = new SingletonFive();
    }

    public static SingletonFive getInstance(){
        return Singleton.INSTANCE;
    }

}
           

這種方式跟餓漢式方式采用的機制類似,但又有不同。兩者都是采用了類裝載的機制來保證初始化執行個體時隻有一個線程。不同的地方在餓漢式方式是隻要Singleton類被裝載就會執行個體化,沒有Lazy-Loading的作用,而靜态内部類方式在Singleton類被裝載時并不會立即執行個體化,而是在需要執行個體化時,調用getInstance方法,才會裝載SingletonInstance類,進而完成Singleton的執行個體化。

類的靜态屬性隻會在第一次加載類的時候初始化,是以在這裡,JVM幫助我們保證了線程的安全性,在類進行初始化時,别的線程是無法進入的。

優點:避免了線程不安全,延遲加載,效率高

繼續閱讀