你是個人惟一,我是你的單例。java
大情至簡、大愛至專
設計模式不少,最 浪漫 的莫過於單例。
由於單例的重要性和易用性,被你們普遍的應用於編碼當中。
正由於其易用,反而被你們忽略了其重要性,產生了一些沒必要要的麻煩。git
有的對象其實咱們只須要一個,好比說:線程池、緩存、對話框、註冊表、日誌、驅動程序等。事實上,這類對象也只能有一個實例,若是製造了多個實例,就會致使許多問題產生。github
若是將對象賦值給一個全局變量,那麼必須在程序一開始就建立好對象,萬一這個對象很是耗費資源,而程序在本次執行中卻沒有用到,就造成了浪費。
而單例模式能夠在須要的時候才建立對象,==這就是延遲實例化==。設計模式
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方法時這種處理方式仍是很是簡潔實用的。
那麼如何在線程安全的狀況下又不下降程序的性能呢?這就是第二種單例模式。
複製代碼
之因此稱爲餓漢式,是由於其在本類初始化時就==急切的建立類對象==,在調用getInstance方法時直接返回建立好的對象。bash
public class MyClass {
// 靜態的本類成員變量
private static MyClass myClass = new MyClass();
//私有的構造器
private MyClass() {
}
//向外界提供的靜態方法
public static MyClass getInstance() {
return myClass;
}
}
複製代碼
這種方式在本類建立時就實例化本類對象,因此==避開了延遲實例化==,是一種線程安全的作法。
其缺點是依然靜態初始化本類對象。
如何在使用延遲實例化的方式下使用線程安全的單例模式, 這就提到了第三種單例模式。微信
利用==雙重加鎖==,首先檢查是否實例已經建立,若是還沒有建立,才進行==同步==。
這樣一來,只有第一次會同步,這正是咱們想要的。多線程
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對象只有一個實例。
複製代碼
單例模式有四種常見的形式:
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
以上文章都可轉載,轉載請註明原創。