單例模式確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供了全局訪問的方法。git
單例模式的三個特色:github
單例模式的應用有,Windows 裏的任務管理器(一個系統中只有一個)、網站計數器(實現多個頁面的計數同步)、數據庫鏈接池(減小資源損耗)等。數據庫
根據單例模式的特色,咱們來實現單例模式,在類中提供一個靜態方法來獲取這個惟一的實例對象,給其餘類提供實例,而且這個實例對象不能直接使用 new 建立,因此構造方法要聲明成私有,這即是最簡單的單例模式實現。這樣的實如今單線程環境中,固然沒問題,可是咱們還要考慮多線程環境下的安全實現。編程
下面是對單例模式的各類實現,而且對每種實現方法都在多線程環境中作了測試,全部代碼都在個人 GitHub 倉庫中中,傳送門。該倉庫還在完成中,用 Java 實現 23 種設計模式,並對設計原則和每種設計模式作出詳細的分析,感興趣的能夠 fork 或者 star 哦,也歡迎小夥伴參與該倉庫的完成。設計模式
經過 getInstance() 方法獲得單例對象,單例對象在須要的時候才被延遲建立,因此稱之爲懶漢式。可是在多線程環境中,因爲這個 getInstance() 方法可能被多個線程同時調用,這極可能會建立多個實例,因此這種實如今多線程環境下是不安全的。安全
給 getInstance() 加上 synchronized 關鍵字後,能夠保證這個方法在同一時間只能被一個線程調用,多個線程調用這個方法要排隊依次調用,這就保證了只會建立一個單例對象,在多線程環境下是安全的。微信
相比於上面的懶漢式,餓漢式在類加載的時候就會建立實例對象,在 getInstance() 方法直接返回建立好的對象,簡單直接,在多線程環境下也是安全的。 多線程
針對於上面的線程安全的懶漢式加載,這種實現方式不是直接給方法加上 synchronized 關鍵字,而是在 getInstance() 方法作雙重檢查來解決線程不安全的問題。這種方式容許多個線程同時調用該方法,可是在方法中會進行兩次檢查,第一次檢查實例是否已經存在,若是不存在才進入下面的同步代碼塊,線程安全的建立實例,若是實例真的不存在(避免這是有其餘線程建立好了,再次建立新的實例)纔會建立實例。這種方式理論上要比直接使用 synchronized 關鍵字性能要高,可是對於不一樣虛擬機對 volatile 關鍵字的優化,優點並不明顯。性能
建立一個靜態內部類,來建立實例,和上面餓漢式相比,雖然都是直接 new 實例,可是這種方式在外部類加載時,靜態內部類並不會被加載。只有在第一次調用 getInstance() 方法時,纔會顯式的加載靜態內部類,建立實例,也是一種延遲(懶)建立方式。測試
枚舉實現單例模式是 Java 大牛們比較推薦的,由於這種方式實現很是簡單,而且這種方式可是大多數單例模式的實現並非這種方式。這種方式須要開發者對枚舉有清晰的認識,這裏也簡單的回顧一下枚舉的基本知識。
枚舉是在 Java1.5 以後出現的,能夠更加簡單的定義常量,經過反編譯,咱們能夠發現枚舉其實也是一個 Java 類,這個類繼承自 Enum 接口,定義的枚舉對象會被加上 static final 關鍵字,這就是咱們不用枚舉時聲明常量的方式,另外在 static 靜態代碼塊中初始化枚舉對象,枚舉的構造方法被加上了 private 關鍵字,防止其餘類建立新的枚舉對象實例。雖然前面幾種方式沒法直接使用 new 建立新的實例,可是能夠用反射來繞過 private 限制,而枚舉卻有自帶的序列化機制、防止反射攻擊形成屢次實例化、線程安全的優勢,從這些地方咱們均可以看出使用枚舉是實現單例模式的絕佳方式。
根據對資源加載時機的須要,來選擇合適的單例模式實現方式,若是是懶加載方式,能夠選擇懶漢方式和雙重校驗鎖方式;若是將資源加載的時間提早來達到使用時的快速體驗,能夠選擇餓漢方式;若是涉及到序列化建立單例對象,能夠選擇枚舉方式。
單例模式的優勢是提供了對惟一實例的訪問控制,能夠節約系統資源,但缺點是單例類的職責太重,而且缺乏抽象層難以擴展,不太符合單一職責原則。
想看更多編程文章,歡迎關注下方的微信公衆號哦。