每天一個設計模式-4 單例模式(Singleton) 1.實際生活的例子 有一天,你的自行車的某個螺絲釘鬆了,修車鋪離你家比較遠,而附近的五金店有賣扳手;因此,你決定去五金店買一個扳手,自己把螺絲釘固定緊。不一會兒,自行車就被你修好了;首先,這個扳手你不會扔掉,下次用的時候直接找出來就用了。好,今天 ...
每天一個設計模式-4 單例模式(Singleton)
1.實際生活的例子
有一天,你的自行車的某個螺絲釘鬆了,修車鋪離你家比較遠,而附近的五金店有賣扳手;因此,你決定去五金店買一個扳手,自己把螺絲釘固定緊。不一會兒,自行車就被你修好了;首先,這個扳手你不會扔掉,下次用的時候直接找出來就用了。好,今天的主題出來了“單例模式”。
2.與變成關聯
在上面的例子中,能找出幾個關鍵字:“買一個扳手”,“螺絲釘固定緊”,“不會扔掉扳手”,“下次用直接找出來”。我們結合標題和這幾個關鍵字好好理解一下:
買一個扳手:創建一個實例。
螺絲釘固定緊:這個實例的功能。
不會扔掉扳手:保存這個實例。
下次用直接找出來:從保存的實例中取出來。
ok,今天我們學習的重點就是這些。
3.單例模式的定義
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
分析:“僅有一個實例”,如何做到僅有一個實例呢?我們知道,java中實例的生成需要通過構造函數,外部通過這個構造函數生成對應的對象,那麼如果我們把構造函數私有化不就可以做到外部無法實例化該對象了嗎;但是,該如何調用這個實例的方法呢?我們可以在該類內部執行構造函數,並將構造後的對象保存在該類的屬性中,並提供一個全局的訪問點供外部調用,因為不能在外部生成該類實例,所以這個全局訪問點被static修飾,這樣我們就可以直接通過類訪問這個方法。
好的!!下麵放出代碼。
4.類圖
4.代碼
實現單利模式,有兩種方法,一種是懶漢式,另一種是餓漢式,最終都是單例模式,只是實現方式略有不同。下麵先放出代碼:
懶漢式實現:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class BanShou { private static BanShou savedBanShou=null; public void finalize() throws Throwable { } private BanShou(){ System.out.println("我買了一個扳手"); } public static BanShou getInstance(){ if(savedBanShou==null){ savedBanShou = new BanShou(); return savedBanShou; } System.out.println("嘿,我找到了我以前買的扳手,這下可以不用再買了"); return savedBanShou; } public void repaire(){ System.out.println("擰緊螺絲釘"); } }扳手類
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Client { public static void main(String[] args) { for(int i=0;i<3;i++){ System.out.println("這是第"+i+"次用到了扳手"); BanShou.getInstance().repaire();; } } }客戶類
餓漢式實現:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class BanShou2 { private static BanShou2 savedBanShou=new BanShou2(); public void finalize() throws Throwable { } private BanShou2(){ System.out.println("我買了一個扳手"); } public static BanShou2 getInstance(){ return savedBanShou; } public void repaire(){ System.out.println("擰緊螺絲釘"); } }餓漢式扳手類
測試結果:
這是第0次用到了扳手
我買了一個扳手
擰緊螺絲釘
這是第1次用到了扳手
嘿,我找到了我以前買的扳手,這下可以不用再買了
擰緊螺絲釘
這是第2次用到了扳手
嘿,我找到了我以前買的扳手,這下可以不用再買了
擰緊螺絲釘
5.模式講解
在上面的代碼中看到單例模式可以用兩種方式實現,那麼他們的區別是什麼呢?首先,先從名稱上理解一下,懶漢式:因為很懶,所以做事都是拖到最後,不得不做時才去做;餓漢式:因為很餓,所以做事很急。體會一下應該就能懂了。↓↓↓↓懶漢式
public static BanShou getInstance(){ if(savedBanShou==null){ savedBanShou = new BanShou(); return savedBanShou; } System.out.println("嘿,我找到了我以前買的扳手,這下可以不用再買了"); return savedBanShou; }
獲取實例時,先去判斷是否為空,如果為空,再去創建(拖到最後,不得不做時才去做)。
↓↓↓↓餓漢式
private static BanShou2 savedBanShou=new BanShou2();
public static BanShou2 getInstance(){ return savedBanShou; }
獲取實例前就已經創建好了,無論你用不用(做事很急)。
在懶漢式實現方式中也涉及了延遲載入的思想(LazyLoad),通俗的說就是:一開始時不要載入資源或數據,一直等,等到馬上就要使用這個資源或者數據了,躲不過了才去載入,這在實際開發中是一種常見的思想,儘可能的節約資源。
餓漢式和懶漢式的實現還體現了緩存的思想:當某個文件,或資料庫數據被頻繁使用,如果每次都從文件或資料庫中讀取,速度會變得很慢,如果把這些數據放到記憶體中,每次取數據時都從緩存中提取,那麼速度便會得到很大的提升。
private static BanShou2 savedBanShou=new BanShou2();
private static BanShou savedBanShou=null;
這兩行代碼就是起到了緩存的作用。
6.單利模式的線程問題
懶漢式實現方式是線程不安全的,比如:
如何解決呢?想讓懶漢式變成線程安全的,最簡單的辦法就是用synchronized
public static synchronized BanShou getInstance()
但這樣做會降低系統性能,因為線程互斥,每次都要等待。
有一種雙重加鎖方式可以解決這個問題。雙重加鎖方式就是:
並不是每次進入getInstance方法都需要同步,而是先不同步,進入方法過後,先檢查實例是否存在,如果不存在才進入下麵的同步塊,這是第一重檢查。進入同步塊過後,再次檢查實例是否存在,如果不存在,就在同步的情況下創建實例,這是第二重檢查。這樣,就只需要同步一次,從而減少了多次在同步情況下進行判斷所浪費的時間。雙重加鎖機制的實現會使用一個關鍵字volatile,它的意思是:被volatile修飾的變數的值,將不會被本地線程緩存,所有對該變數的讀寫都是直接操作共用記憶體,從而確保多個線程能正確的處理該變數。
但這種方式用到了volatile,它會屏蔽掉虛擬機中的一些必要的代碼優化,所以運行效率不是很高,下麵推薦一個更好的單例實現方式:
直接放出源代碼吧,也涉及一些其他的基礎知識,這裡就不寫了,畢竟這麼晚了;今天比較忙,所以現在才更新。不好意了。
更好的單例模式實現源代碼:
public class Singleton { //這部分只有被調用到時,才會被裝載,既線程安全,有延遲載入 private static class SingletonHolder{ private static Singleton instance = new Singleton(); } private Singleton(){ } public static Singleton getInstance(){ return SingletonHolder.instance; } }
7.總結
實現單例模式,就必須將構造器私有化,在類內部實例化,並提供全局的訪問點,從而達到控制實例的數目的目的
-------博主寫博客不容易,轉載請註明出處,謝謝:http://www.cnblogs.com/xiemubg/p/5954949.html