Java学习-----单例模式
一.問題引入
偶然想想到的如果把Java的構(gòu)造方法弄成private,那里面的成員屬性是不是只有通過static來訪問呢;如果構(gòu)造方法是private的話,那么有什么好處呢;如果構(gòu)造方法是private的話,會不更好的封裝該內(nèi)呢?我主要是應(yīng)用在使用普通類模擬枚舉類型里,后來發(fā)現(xiàn)這就是傳說中的單例模式。構(gòu)造函數(shù)弄成private 就是單例模式,即不想讓別人用new 方法來創(chuàng)建多個對象,可以在類里面先生成一個對象,然后寫一個public static方法把這個對象return出去。(eg:public 類名 getInstancd(){return 你剛剛生成的那個類對象;}),用static是因為你的構(gòu)造函數(shù)是私有的,不能產(chǎn)生對象,所以只能用類名調(diào)用,所有只能是靜態(tài)函數(shù)。成員變量也可以寫getter/setter供外界訪問的。如果誰要用這個類的實例就用有興趣的讀者參看我的這一篇博文http://www.cnblogs.com/hxsyl/archive/2013/03/18/2966360.html。
第一個代碼不是單例模式,也就是說不一定只要構(gòu)造方法是private的就是單例模式。
?
class A(){private A(){}public name;pulbic static A creatInstance(){return new A();}}A a = A.createInstance(); a.name; //name 屬性?
public class single{ private static final single s=new single(); private single(){ } public static single getInstance(){ return s; } }?
二.單例模式概念及特點
java中單例模式是一種常見的設(shè)計模式,單例模式分三種:懶漢式單例、餓漢式單例、登記式單例三種。
單例模式有一下特點:
1、單例類只能有一個實例。
2、單例類必須自己自己創(chuàng)建自己的唯一實例。
3、單例類必須給所有其他對象提供這一實例。
單例模式確保某個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例。在計算機(jī)系統(tǒng)中,線程池、緩存、日志對象、對話框、打印機(jī)、顯卡的驅(qū)動程序?qū)ο蟪1辉O(shè)計成單例。這些應(yīng)用都或多或少具有資源管理器的功能。每臺計算機(jī)可以有若干個打印機(jī),但只能有一個Printer Spooler,以避免兩個打印作業(yè)同時輸出到打印機(jī)中。每臺計算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調(diào)用。總之,選擇單例模式就是為了避免不一致狀態(tài),避免政出多頭。
正是由于這個特 點,單例對象通常作為程序中的存放配置信息的載體,因為它能保證其他對象讀到一致的信息。例如在某個服務(wù)器程序中,該服務(wù)器的配置信息可能存放在數(shù)據(jù)庫或 文件中,這些配置數(shù)據(jù)由某個單例對象統(tǒng)一讀取,服務(wù)進(jìn)程中的其他對象如果要獲取這些配置信息,只需訪問該單例對象即可。這種方式極大地簡化了在復(fù)雜環(huán)境 下,尤其是多線程環(huán)境下的配置管理,但是隨著應(yīng)用場景的不同,也可能帶來一些同步問題。
?
三.典型例題
首先看一個經(jīng)典的單例實現(xiàn)。
?
public class Singleton {private static Singleton uniqueInstance = null;private Singleton() {// Exists only to defeat instantiation. }public static Singleton getInstance() {if (uniqueInstance == null) {uniqueInstance = new Singleton();}return uniqueInstance;}// Other methods... }?
Singleton通過將構(gòu)造方法限定為private避免了類在外部被實例化,在同一個虛擬機(jī)范圍內(nèi),Singleton的唯一實例只能通過getInstance()方法訪問。(事實上,通過Java反射機(jī)制是能夠?qū)嵗瘶?gòu)造方法為private的類的,那基本上會使所有的Java單例實現(xiàn)失效。此問題在此處不做討論,姑且掩耳盜鈴地認(rèn)為反射機(jī)制不存在。)
但是以上實現(xiàn)沒有考慮線程安全問題。所謂線程安全是指:如果你的代碼所在的進(jìn)程中有多個線程在同時運(yùn)行,而這些線程可能會同時運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。或者說:一個類或者程序所提供的接口對于線程來說是原子操作或者多個線程之間的切換不會導(dǎo)致該接口的執(zhí)行結(jié)果存在二義性,也就是說我們不用考慮同步的問題。顯然以上實現(xiàn)并不滿足線程安全的要求,在并發(fā)環(huán)境下很可能出現(xiàn)多個Singleton實例。
?
public class TestStream {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;} //該類只能有一個實例private TestStream(){} //私有無參構(gòu)造方法//該類必須自行創(chuàng)建//有2種方式/*private static final TestStream ts=new TestStream();*/private static TestStream ts1=null;//這個類必須自動向整個系統(tǒng)提供這個實例對象public static TestStream getTest(){if(ts1==null){ts1=new TestStream();}return ts1;}public void getInfo(){System.out.println("output message "+name);}}?
public class TestMain {public static void main(String [] args){TestStream s=TestStream.getTest();s.setName("張孝祥");System.out.println(s.getName());TestStream s1=TestStream.getTest();s1.setName("張孝祥");System.out.println(s1.getName());s.getInfo();s1.getInfo();if(s==s1){System.out.println("創(chuàng)建的是同一個實例");}else if(s!=s1){System.out.println("創(chuàng)建的不是同一個實例");}else{System.out.println("application error");}}}?
運(yùn)行結(jié)果:
張孝祥
張孝祥
output message 張孝祥
output message 張孝祥
創(chuàng)建的是同一個實例
?
結(jié)論:由結(jié)果可以得知單例模式為一個面向?qū)ο蟮膽?yīng)用程序提供了對象惟一的訪問點,不管它實現(xiàn)何種功能,整個應(yīng)用程序都會同享一個實例對象。
其次,下面是單例的三種實現(xiàn)。
1.餓漢式單例類
飛哥下面這個可以不加final,因為靜態(tài)方法只在編譯期間執(zhí)行一次初始化,也就是只會有一個對象。
?
//餓漢式單例類.在類初始化時,已經(jīng)自行實例化 public class Singleton1 {//私有的默認(rèn)構(gòu)造子private Singleton1() {}//已經(jīng)自行實例化 private static final Singleton1 single = new Singleton1();//靜態(tài)工廠方法 public static Singleton1 getInstance() {return single;}}?
2.懶漢式單例類
那個if判斷確保對象只創(chuàng)建一次。
?
//懶漢式單例類.在第一次調(diào)用的時候?qū)嵗?public class Singleton2 {//私有的默認(rèn)構(gòu)造子private Singleton2() {}//注意,這里沒有final private static Singleton2 single=null;//靜態(tài)工廠方法 public synchronized static Singleton2 getInstance() {if (single == null) { single = new Singleton2();} return single;}}?
3.登記式單例類
?
import java.util.HashMap;import java.util.Map;//登記式單例類.//類似Spring里面的方法,將類名注冊,下次從里面直接獲取。public class Singleton3 {private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();static{Singleton3 single = new Singleton3();map.put(single.getClass().getName(), single);}//保護(hù)的默認(rèn)構(gòu)造子protected Singleton3(){}//靜態(tài)工廠方法,返還此類惟一的實例public static Singleton3 getInstance(String name) {if(name == null) {name = Singleton3.class.getName();System.out.println("name == null"+"--->name="+name);}if(map.get(name) == null) {try {map.put(name, (Singleton3) Class.forName(name).newInstance());} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}return map.get(name);}//一個示意性的商業(yè)方法public String about() { return "Hello, I am RegSingleton."; } public static void main(String[] args) {Singleton3 single3 = Singleton3.getInstance(null);System.out.println(single3.about());}}?
四.單例對象作配置信息管理時可能會帶來的幾個同步問題
1.在多線程環(huán)境下,單例對象的同步問題主要體現(xiàn)在兩個方面,單例對象的初始化和單例對象的屬性更新。
本文描述的方法有如下假設(shè):
a. 單例對象的屬性(或成員變量)的獲取,是通過單例對象的初始化實現(xiàn)的。也就是說,在單例對象初始化時,會從文件或數(shù)據(jù)庫中讀取最新的配置信息。
b. 其他對象不能直接改變單例對象的屬性,單例對象屬性的變化來源于配置文件或配置數(shù)據(jù)庫數(shù)據(jù)的變化。
1.1單例對象的初始化
首先,討論一下單例對象的初始化同步。單例模式的通常處理方式是,在對象中有一個靜態(tài)成員變量,其類型就是單例類型本身;如果該變量為null,則創(chuàng)建該單例類型的對象,并將該變量指向這個對象;如果該變量不為null,則直接使用該變量。
這種處理方式在單線程的模式下可以很好的運(yùn)行;但是在多線程模式下,可能產(chǎn)生問題。如果第一個線程發(fā)現(xiàn)成員變量為null,準(zhǔn)備創(chuàng)建對象;這是第二 個線程同時也發(fā)現(xiàn)成員變量為null,也會創(chuàng)建新對象。這就會造成在一個JVM中有多個單例類型的實例。如果這個單例類型的成員變量在運(yùn)行過程中變化,會 造成多個單例類型實例的不一致,產(chǎn)生一些很奇怪的現(xiàn)象。例如,某服務(wù)進(jìn)程通過檢查單例對象的某個屬性來停止多個線程服務(wù),如果存在多個單例對象的實例,就 會造成部分線程服務(wù)停止,部分線程服務(wù)不能停止的情況。
1.2單例對象的屬性更新
通常,為了實現(xiàn)配置信息的實時更新,會有一個線程不停檢測配置文件或配置數(shù)據(jù)庫的內(nèi)容,一旦發(fā)現(xiàn)變化,就更新到單例對象的屬性中。在更新這些信 息的時候,很可能還會有其他線程正在讀取這些信息,造成意想不到的后果。還是以通過單例對象屬性停止線程服務(wù)為例,如果更新屬性時讀寫不同步,可能訪問該 屬性時這個屬性正好為空(null),程序就會拋出異常。
下面是解決方法
?
//單例對象的初始化同步 public class GlobalConfig {private static GlobalConfig instance = null;private Vector properties = null;private GlobalConfig() {//Load configuration information from DB or file//Set values for properties }private static synchronized void syncInit() {if (instance == null) {instance = new GlobalConfig();}}public static GlobalConfig getInstance() {if (instance == null) {syncInit();}return instance;}public Vector getProperties() {return properties;}}?
這種處理方式雖然引入了同步代碼,但是因為這段同步代碼只會在最開始的時候執(zhí)行一次或多次,所以對整個系統(tǒng)的性能不會有影響。
單例對象的屬性更新同步。
參照讀者/寫者的處理方式,設(shè)置一個讀計數(shù)器,每次讀取配置信息前,將計數(shù)器加1,讀完后將計數(shù)器減1.只有在讀計數(shù)器為0時,才能更新數(shù)據(jù),同時要阻塞所有讀屬性的調(diào)用。
代碼如下:
?
public class GlobalConfig {private static GlobalConfig instance;private Vector properties = null;private boolean isUpdating = false;private int readCount = 0;private GlobalConfig() {//Load configuration information from DB or file//Set values for properties }private static synchronized void syncInit() {if (instance == null) {instance = new GlobalConfig();}}public static GlobalConfig getInstance() {if (instance==null) {syncInit();}return instance;}public synchronized void update(String p_data) {syncUpdateIn();//Update properties }private synchronized void syncUpdateIn() {while (readCount > 0) {try {wait();} catch (Exception e) {}}}private synchronized void syncReadIn() {readCount++;}private synchronized void syncReadOut() {readCount--;notifyAll();}public Vector getProperties() {syncReadIn();//Process data syncReadOut();return properties;}}?
采用"影子實例"的辦法具體說,就是在更新屬性時,直接生成另一個單例對象實例,這個新生成的單例對象實例將從數(shù)據(jù)庫或文件中讀取最新的配置信息;然后將這些配置信息直接賦值給舊單例對象的屬性。
?
public class GlobalConfig {private static GlobalConfig instance = null;private Vector properties = null;private GlobalConfig() {//Load configuration information from DB or file//Set values for properties }private static synchronized void syncInit() {if (instance = null) {instance = new GlobalConfig();}}public static GlobalConfig getInstance() {if (instance = null) {syncInit();}return instance;}public Vector getProperties() {return properties;}public void updateProperties() {//Load updated configuration information by new a GlobalConfig objectGlobalConfig shadow = new GlobalConfig();properties = shadow.getProperties();}}?
注意:在更新方法中,通過生成新的GlobalConfig的實例,從文件或數(shù)據(jù)庫中得到最新配置信息,并存放到properties屬性中。上面兩個方法比較起來,第二個方法更好,首先,編程更簡單;其次,沒有那么多的同步操作,對性能的影響也不大。
?
?五.結(jié)束語
參考了很多資料才整理出了該內(nèi)容,請大家多多指教,期間受益頗多,感覺到OS很重要,等把網(wǎng)絡(luò)版的俄羅斯方塊搞好后就去復(fù)習(xí)OS……加油,我是最棒的!!!
? 參考文獻(xiàn):http://www.cnblogs.com/whgw/archive/2011/10/05/2199535.html,http://blog.csdn.net/leesidong/article/details/6024455
轉(zhuǎn)載于:https://www.cnblogs.com/dragon1013/p/5036027.html
總結(jié)
以上是生活随笔為你收集整理的Java学习-----单例模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: activity切换动画特效
- 下一篇: Servelt学习笔记之二——使用Ser