最浪漫的模式——單例

引言

你是個人惟一,我是你的單例。java

大情至簡、大愛至專
設計模式不少,最 浪漫 的莫過於單例。
由於單例的重要性和易用性,被你們普遍的應用於編碼當中。
正由於其易用,反而被你們忽略了其重要性,產生了一些沒必要要的麻煩。git

一、爲何須要單例

有的對象其實咱們只須要一個,好比說:線程池、緩存、對話框、註冊表、日誌、驅動程序等。事實上,這類對象也只能有一個實例,若是製造了多個實例,就會致使許多問題產生。github

二、初始化全局變量和單例模式的區別

若是將對象賦值給一個全局變量,那麼必須在程序一開始就建立好對象,萬一這個對象很是耗費資源,而程序在本次執行中卻沒有用到,就造成了浪費。
而單例模式能夠在須要的時候才建立對象,==這就是延遲實例化==。設計模式

1、第一種單例模式:懶漢式

java在實例化對象的時候須要用到new關鍵字,前提是實例化的類要有公開的構造器,那麼就能夠經過new來建立多個對象。
若是被實例化的類沒有公開的構造器,而是一個私有的構造器,那麼如何來獲取類對象呢?
這也是單例的巧妙之處,private的構造器是沒法在本類以外調用的,因此只能在本類中實例化。
那麼外部想要獲得這個實例,就須要提供一個靜態的方法將實例化好的對象返回出去。
懶漢式單例的要素:靜態的本類全局變量、私有的構造器、getInstance方法緩存

public class MyClass {
        //靜態的本類成員變量
        private static MyClass myClass;
        //私有的構造器
        private MyClass() {
        }
        // 向外界提供的靜態方法
        public static MyClass getInstance() {
           if (myClass == null) {
              myClass = new MyClass();
           }
           return myClass;
        }
     }
複製代碼

這就是常見的懶漢式單例,由於==使用的時候纔開始建立(延遲實例化)==,因此稱爲「懶」漢式。
懶漢式雖然易用,可是其在線程安全方面仍有一些問題:
假設有兩個線程Thread1和Thread2同時調用getInstance方法。
線程2在線程1已經建立出MyClass對象而且已經刷新內存以後調用不會出現問題。
可是線程1剛建立出MyClass對象時尚未刷新內存以前,線程2仍是認爲myClass對象爲null。
那麼就會建立出第二個myClass對象,因此線程是不安全的。
這種問題能夠經過線程鎖來解決:安全

//向外界提供的靜態方法
        public synchronized static MyClass getInstance() {
           if (myClass == null) {
              myClass = new MyClass();
           }
           return myClass;
        }
複製代碼
使用線程鎖能夠規避多線程狀況下產生不一樣的myClass對象,可是這種方法仍有弊端。  
由於synchronized關鍵字會形成線程等待而下降程序的性能,同步一個方法可能形成程序運行的效率降低十倍百倍。  
可是在不須要頻繁操做getInstance方法時這種處理方式仍是很是簡潔實用的。  
那麼如何在線程安全的狀況下又不下降程序的性能呢?這就是第二種單例模式。  
複製代碼

2、第二種單例模式:餓漢式

之因此稱爲餓漢式,是由於其在本類初始化時就==急切的建立類對象==,在調用getInstance方法時直接返回建立好的對象。bash

public class MyClass {
    // 靜態的本類成員變量
    private static MyClass myClass = new MyClass();
    //私有的構造器
    private MyClass() {
    }
    //向外界提供的靜態方法
    public static MyClass getInstance() {
        return myClass;
    }
}
複製代碼

這種方式在本類建立時就實例化本類對象,因此==避開了延遲實例化==,是一種線程安全的作法。
其缺點是依然靜態初始化本類對象。
如何在使用延遲實例化的方式下使用線程安全的單例模式, 這就提到了第三種單例模式。微信

3、第三種單例模式:雙重加鎖

利用==雙重加鎖==,首先檢查是否實例已經建立,若是還沒有建立,才進行==同步==。
這樣一來,只有第一次會同步,這正是咱們想要的。多線程

public class MyClass {
    // 靜態的本類成員變量
    private volatile static MyClass myClass;
    // 私有的構造器
    private MyClass() {
    }
    // 像外界提供的靜態方法
    public static MyClass getInstance() {
        if (myClass == null) {
            synchronized (MyClass.class) {
                if (myClass == null) {
                    myClass = new MyClass();
                }
            }
        }
        return myClass;
    }
}
複製代碼
volatile 關鍵字保證線程的併發下myClass可以強制刷新內存,通知其他線程對象發生的改變。  
synchronized 在第一次初始化時進行同步,在同步過程當中再進行一次判斷,從而保證MyClass對象只有一個實例。
複製代碼

4、總結

單例模式有四種常見的形式:
1:懶漢式:簡單、經典、線程不安全
2:同步懶漢式:簡單、經典、線程安全、性能下降
3:餓漢式:簡單、經典、線程安全、可是沒有使用延遲實例化
4:雙重加鎖:相對複雜、線程安全、使用延遲實例化
併發


長路漫漫,菜不是原罪,墮落纔是原罪。
個人CSDN:blog.csdn.net/wuyangyang_…
個人簡書:www.jianshu.com/u/20c2f2c35…
個人掘金:juejin.im/user/58009b…
個人GitHub:github.com/wuyang2000
我的網站:www.xiyangkeji.cn
我的app(茜茜)蒲公英鏈接:www.pgyer.com/KMdT
個人微信公衆號:茜洋 (按期推送優質技術文章,歡迎關注)
Android技術交流羣:691174792

以上文章都可轉載,轉載請註明原創。

相關文章
相關標籤/搜索