1、什麼叫設計模式:
設計模式的概念首先來源於其它行業:建築業,在早起建房子的時候,肯定是經驗缺乏、顯得雜亂無序的,這就會造成很多問題,在行業發展過程,通過不斷的經驗積累,前輩們針對這些問題提出了合理解決方案,這就是設計模式,參照設計模式往往可以解決很多問題,在計算機編程方面,也會出現類似問題,所以牛人們把這些解決問題的方案進行歸類和總結,形成了面向對象編程的23種設計模式。
2、單例模式(特點):
Java中單例模式定義:“一個類有且僅有一個實例,並且自行實例化向整個系統提供。”通過使用設計模式可以讓我們的代碼復用性更高,可維護性更高,讓你的代碼寫的更優雅。
3、動機(單例模式的由來):
1、首先對象可以看著類的實體化副本,就像圖紙和圖紙所打造的實體,同一個類的不同對象,是屬性的差異,但是有時候我們並不需要內存中存在該類多個對象,比如一個系統只能有一個窗口管理器或文件系統,如在Windows中就只能打開一個任務管理器。如果不使用機制對窗口對象進行唯一化,將彈出多個窗口,如果這些窗口顯示的內容完全一致,則是重復對象,浪費內存資源;如果這些窗口顯示的內容不一致,則意味著在某一瞬間系統有多個狀態,與實際不符,也會給用戶帶來誤解,不知道哪一個才是真實的狀態。因此有時確保系統中某個對象的唯一性即一個類只能有一個實例非常重要;再比如軟件的配置文件有多個用戶去獲取配置信息的時候,如果也是創建多個對象,他們是分別操作不同的對象,數據是不能實時共享的,而如果內存是一個對象,則可以解決這個問題。
2、如果一個類的對象有可能在內存出現大量重復的時候,某種程度限制對象的創建,可以減少內存的消耗,當然這是一個比較牽強的動機。
4、思路(解決問題的歷程):
解決如上問題,其實首要想到的方法就是通過給類中成員添加靜態(static)修飾的方式,這樣就形成了數據的共享,但是這裡存在的主要的問題就是靜態成員在內存的駐留時間過長,至少比對象長,所有通過唯一對象的方式更加適合。
如何保持對象的唯一性:
1、不允許其它類中創建(new)該類的對象,因為這是無法控制創建數量的。
2、必須在該類中提供一個該類的唯一實例。
3、對外提供一個方法,以便其它類可以獲取該實例。
具體辦法:
第一點:不允許其它類中創建(new)該類的對象,因對象的創建必須調用構造函數,所以把默認構造函數私有化。
第二點:可以在類中創建(new)本類的對象。
第三點:構建的一個方法,返回該類的對象。
代碼歷程:
public class Singleton { private Singleton(){};//滿足第一點 public Singleton getInstance(){//似乎滿足第二點 return new Singleton;//似乎滿足第三點 } }
問題:為什麼第二點和第三點加上似乎呢?仔細思考一下就會發現問題:
第二點中,因為該類不能創建對象,所以getInstance()無法調用;第三點中,假如getInstance()可以調用,但每一次調用都會創建一個新的對象,不符合要求。 解決辦法: 給方法加上靜態修飾(static),通過類名調用。創建一個對象的引用變量,指向該類的對象,在getInstance()中返回對象的引用。 修改後:public class Singleton { Singleton instance = new Singleton(); private Singleton(){}; public static Singleton getInstance(){ return instance; } }問題: 在靜態方法getInstance中是無法調用非靜態成員變量的,所以需要把instance變成靜態成員。如第一條成立,可以通過Singleton.instance獲取到對象的實例,這樣等效於調用getInstance方法,所以最好加上私有修飾符,禁止該途徑調用對象實例。 一、正確代碼(餓漢式):
public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){}; public static Singleton getInstance(){ return instance; } }
這個時候我們以為掌握了單例模式,其實是掌握了,但僅僅是單例設計模式的一種寫法,如上寫法稱為”餓漢式“,因instance是靜態成員變量,所以在jvm對該類加載過程中,就進行對象的實例化,所以是提前創建實例。
另外一種寫法,就是在getInstance調用時才進行對象的創建,這種寫法又稱為”延遲加載“或者”懶漢式“。 二、懶漢式(延遲加載)。public class Singleton { private static Singleton instance = null; private Singleton(){}; public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }問題:懶漢式的寫法,在多線程中會出現問題,即創建多個對象,如下圖所示:
public class Singleton { private static Singleton instance = null; private Singleton(){}; public static Singleton getInstance(){ if(instance == null){ --> 一個A線程進入並掛起。 --> 此時一個B線程進入,這種情況會造成兩個線程都會執行new Singleton操作,這樣就會在內存存在多個該類對象。 instance = new Singleton(); } return instance; } }
public class Singleton { private static Singleton instance = null; private Singleton(){}; public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
另一種寫法:
public class Singleton { private static Singleton instance = null; private Singleton(){}; public static Singleton getInstance(){ synchronized(Singleton.class){ //同步代碼塊,鎖不能為this,因為該方法是靜態方法,不能獲取到this對象。 if(instance == null){ instance = new Singleton(); } return instance; } } }問題:每次調用getInstance時都會判斷鎖,這樣會降低程序的效率。注:鎖(Singleton.class)該類所屬的字節碼文件對象。
四、懶漢式雙重校驗鎖。
public class Singleton {
private 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時,即instance等於null時才會判斷鎖,這樣就提搞了效率。 5、單例模式的使用: 如果僅僅如上面所示的方式 ,去創建這些代碼是沒有意義,正確使用單例模式的方式就是在原有代碼上,加上這些代碼。
添加單例模式前:
public class Test { public static void main(String[] args){ Person p1 = new Person(); p1.setName("Jack"); p1.setAge(18) ; Person p2 = new Person(); p2.setName("David"); p2.setAge(20); System.out.println(p1.getName() + ":" + p1.getAge()); System.out.println(p2.getName() + ":" + p2.getAge()); } } class Person{ private String name ; private int age; public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setAge(int age){ this.age = age; } public int getAge(){ return age; } }
運行結果:
Jack:18
David:20
單例模式後:
public class Test { public static void main(String[] args){ Person p1 = Person.getInstance(); p1.setName("Jack"); p1.setAge(18) ; Person p2 = Person.getInstance(); p2.setName("David"); p2.setAge(20); System.out.println(p1.getName() + ":" + p1.getAge()); System.out.println(p2.getName() + ":" + p2.getAge()); } } class Person{ private String name ; private int age; private static Person instance = new Person();//2.本類中創建該類的對象 private Person(){};//1.私有構造函數 public static Person getInstance(){//3.創建一個方法,向外部提供該類的對象 return instance; } public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setAge(int age){ this.age = age; } public int getAge(){ return age; } }
運行結果:
David:20
David:20
目的:保證了對象的唯一性