你真的會用單例模式嗎?

一、簡介:

 我們經常有這樣的需求:  某一些類應該只存在一個實例  的時候,我們就可以用單例模式來應對.

單例模式:確保一個類只有一個實例,并提供一個全局訪問點.

單例模式是所有設計模式中最簡單的一個,也是大部分人最早知道的一個設計模式.

二、我們經常用的2種單例模式(懶漢式、餓漢式)

(1)餓漢式:

餓漢式單例類.在類初始化時,已經自行實例化

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

餓漢式在類創建的同時就已經創建好一個靜態的對象供系統使用,以后不再改變,所以是線程安全的。

(2)懶漢式

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

備注


Paste_Image.png
 單例模式的懶漢式實現方式體現了延遲加載的思想,什么是延遲加
載呢?  通俗點說,就是一開始不要加載
資源或者數據,一直等,等到馬上就要使用這個資源或者數據了,躲不過去了才加載,所以也稱
Lazy Load,不是懶惰啊,是“延遲加載”,這在實際開發中是一種很常見的思想,盡可能的節約資源。
   單例模式的懶漢式實現還體現了緩存的思想,緩存也是實際開發中非常常見的功能。簡單講就是,如果
某些資源或者數據會被頻繁的使用,而這些資源或數據存儲在系統外部,比如數據庫、硬盤文件等,
那么每次操作這些數據的時候都從數據庫或者硬盤上去獲取,速度會很慢,會造成性能問題。 一個簡單的
解決方法就是:把這些數據緩存到內存里面,每次操作的時候,先到內存里面找,看有沒有這些數據,
如果有,那么就直接使用,如果沒有那么就獲取它,并設置到緩存中,下一次訪問的時候就可以直接從內存
中獲取了。從而節省大量的時間,當然,緩存是一種典型的空間換時間的方案。

兩種單例模式的比較

比較上面兩種寫法:
1、懶漢式是典型的時間換空間,也就是每次獲取實例都會進行判斷,看是否需要創建實例,費判斷的時間,當然,如果一直沒有人使用的話,那就不會創建實例,節約內存空間。
2、 餓漢式是典型的空間換時間,當類裝載的時候就會創建類實例,不管你用不用,先創建出來,然后每次調用的時候,就不需要再判斷了,節省了運行時間。

(3)雙重檢查加鎖

public class Singleton {        

        private volatile static Singleton instance = null;
        private Singleton() {}
        public static Singleton getInstance() {
            //先檢查實例是否存在,如果不存在才進入下面的同步塊
            if (instance == null) {
                //同步塊,線程安全的創建實例
                synchronized (Singleton.class) {
                    //再次檢查實例是否存在,如果不存在才真的創建實例
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }

雙重檢查詳解
可以使用“雙重檢查加鎖”的方式來實現,就可以既實現線程安全,又能夠使性能不受到大的影響。
那么什么是“雙重檢查加鎖”機制呢? 所謂雙重檢查加鎖機制,指的是:并不是每次進入getInstance方法都需要同步,而是先不同步,進入方法過后,先檢查實例是否存在,如果不存在才進入下面的同步塊,這是第一重檢查。進入同步塊過后,再次檢查實例是否存在,如果不存在,就在同步的情況下創建一個實例,這是第二重檢查。這樣一來,就只需要同步一次了,從而減少了多次在同步情況下進行判斷所浪費的時間。 雙重檢查加鎖機制的實現會使用一個關鍵字volatile,它的意思是:被volatile修飾的變量的值,將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存,從而確保多個線程能正確的處理該變量。 這種實現方式既可使實現線程安全的創建實例,又不會對性能造成太大的影響,它只是在第一次創建實例的時候同步,以后就不需要同步了,從而加快運行速度。 注意:在Java1.4及以前版本中,很多JVM對于volatile關鍵字的實現有問題,會導致雙重檢查加鎖的失敗,因此雙重檢查加鎖的機制只能用在Java5及以上的版本。

(4)、靜態內部類單例模式

public class Singleton {

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

當第一次加載Singleton類時并不會初始化INSTANCE,只有在第一次調用getInstance方法時才會導致INSTANCE被初始化。這種方式不僅能夠保證線程安全,也能保證單例對象的唯一性,同時也延長了單例的實例化。

小結

1、雙重檢查非常適用于高并發,我們熟知的開源庫Eventbus,ImageLoader等都是用的雙重檢查鎖方式實現單例

2、單例模式是運用頻率很高的模式,但是,由于在客戶端通常沒有高并發的情況,因此,選擇哪種實現方式都不會有太大的影響。即使如此,出于效率考慮,推薦使用DCL單例(雙重檢查鎖定)和靜態內部類單例模式。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容