單例模式----讀書筆記

單例模式—讀書筆記

單例模式,顧名思義,就是用來創建一個獨一無二的,只能有一個實例的對象的入場券。因爲在實際應用中,有些對象我們只需要一個,比如說,線程池、緩存、對話框、註冊表等。
當然,我們也可以使用全局對象來達到和單例模式一樣的作用,但是全局對象有一些缺點,比如說:如果將對象賦值給一個全局變量,那麼必須在程序一開始就創建好該對象,如果這個對象很耗資源,但是我們又沒有用到它,那就會形成一種浪費。或許我們還可以使用其他方法來達到單例的相同的目的,但是,單例模式很簡單,而且經得住時間考驗,是值得學習的。
單例模式有很多種實現,下面我們來看一下各種實現的優缺點。

方法1

SingleTon.java

public class SingleTon {
    private static SingleTon singleTon;//私有的,不能在類外部訪問,只能在類的內部訪問

    private SingleTon(){

    }

    public static SingleTon getInstance(){
        if (singleTon == null){
            singleTon = new SingleTon();
        }
        return singleTon;
    }
}

SingleTon.kt

class SingleTon private constructor(){
    companion object {
        val instance by lazy { SingleTon() }//用到的時候初始化,且只初始化一次
    }
}

總結

單例模式可以保證在任何時刻都只有一個對象(私有構造器)。一般情況下,常常用來管理共享的資源,例如數據庫連接和線程池。

方法2:線程安全

上面的java代碼在多線程的時候,會出現安全問題。下面我們處理一下多線程的情況:

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

SingleTon.kt

class SingleTon private constructor(){
    companion object {
        private var INSTANCE: SingleTon? = null

        //在Kotlin中加鎖
        @Synchronized
        fun getInstance(): SingleTon {
            if (INSTANCE == null) {
                INSTANCE = SingleTon()
            }
            return INSTANCE!!
        }
    }
}

在上面的方法中,我們在getInstance()方法中,添加了synchoronized關鍵字,可以保證同時只有一個線程進入該方法,不會有兩個線程可以同時進入這方法。

但是,這個方法有一些缺點,只有第一次執行此方法的時候,才需要同步,換句話說,一旦設置了singleTon對象,就不再需要同步這個方法了。不然的話,性能會很低。

改進

1、如果getInstance()方法的性能對應用程序不是很關鍵,那就什麼都別做。
2、使用“餓漢”創建實例,而不是延遲實例化的做法

public class SingleTon{
    private static SingleTon singleTon = new SingleTon();//jvm保證線程安全,且在加載這個類的時候,馬上創建此唯一的單例實例。
    private SingleTon(){

    }

    public static SingleTon getInstance(){
        return singleTon;
    }

}

SingleTon.kt

object SingleTon{
   
}

3、用“雙重檢查加鎖”,在getInstance()中減少使用同步

使用雙重加鎖,首先檢查是否實例已經創建了,如果尚未創建,“才”進行同步,這樣一來,只有第一次需要同步,這正是我們想要的。

public class SingleTon{
    private volatile static SingleTon singleTon;

    private SingleTon(){

    }

    public static SingleTon getInstance(){
        if (singleTon == null){
            synchronized (SingleTon.class){
                if (singleTon == null){
                    singleTon = new SingleTon();
                }
            }
        }
        return singleTon;
    }
}

volatile 關鍵字確保,當singleTon變量被初始化成SingleTon實例時,多個線程正確的處理singleTon變量。

SingleTon.kt

class SingleTon private constructor() {
    companion object {
        val singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            SingleTon()
        }
    }
}

方法3:靜態內部類

SingleTon.java

public class SingleTon{
    private SingleTon(){

    }

    private static class SingleTonHolder{
        private static SingleTon singleTon = new SingleTon();
    }
    private static SingleTon getInstance(){
        return SingleTonHolder.singleTon;
    }
}

靜態內部類的優點是:外部類加載時,不需要立即加載內部類,內部類不加載則不去初始化singleTon,因而不佔用內存。只有當getInstance()方法第一次被調用時,纔會去初始化singleTon,第一個調用getInstance方法會導致虛擬機加載SingleTonHolder這個類,這種方法不僅能確保線程安全,也能保證單例的唯一性,而且這做到了延遲單例的實例化。

在singleTon創建的過程中,是怎麼保證線程安全的呢?如果多個線程同時初始化一個類,那麼虛擬機會保證只會有一個線程去初始化這個類,其他線程都需要阻塞等待,直到初始化完畢。因此靜態內部類的形式的單例可以保證線程安全,也能保證單例的唯一性,同時也延遲了單例的實例化。

SingleTon.kt

class SingleTon private constructor(){
    companion object {//靜態方法
        val instance = Holder.holder
    }

    private object Holder {//私有靜態內部類
        val holder= SingleTon()
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章