您好,歡迎來電子發(fā)燒友網(wǎng)! ,新用戶?[免費(fèi)注冊(cè)]

您的位置:電子發(fā)燒友網(wǎng)>源碼下載>java源碼下載>

單例模式詳解

大小:0.6 MB 人氣: 2017-09-27 需要積分:2

  一。問題引入

  偶然想想到的如果把Java的構(gòu)造方法弄成private,那里面的成員屬性是不是只有通過static來訪問呢;如果構(gòu)造方法是private的話,那么有什么好處呢;如果構(gòu)造方法是private的話,會(huì)不更好的封裝該內(nèi)呢?我主要是應(yīng)用在使用普通類模擬枚舉類型里,后來發(fā)現(xiàn)這就是傳說中的單例模式。構(gòu)造函數(shù)弄成private 就是單例模式,即不想讓別人用new 方法來創(chuàng)建多個(gè)對(duì)象,可以在類里面先生成一個(gè)對(duì)象,然后寫一個(gè)public static方法把這個(gè)對(duì)象return出去。(eg:public 類名 getInstancd(){return 你剛剛生成的那個(gè)類對(duì)象;}),用static是因?yàn)槟愕臉?gòu)造函數(shù)是私有的,不能產(chǎn)生對(duì)象,所以只能用類名調(diào)用,所有只能是靜態(tài)函數(shù)。成員變量也可以寫getter/setter供外界訪問的。

  第一個(gè)代碼不是單例模式,也就是說不一定只要構(gòu)造方法是private的就是單例模式。

  class A(){ privateA(){} publicname; pulbic staticA creatInstance(){ returnnewA(); } } A a = A.createInstance(); a.name; //name 屬性publicclasssingle{ privatestaticfinal single s=newsingle(); privatesingle(){ } publicstaticsingle getInstance(){ returns; } }二。單例模式概念及特點(diǎn)

  java中單例模式是一種常見的設(shè)計(jì)模式,單例模式分三種:懶漢式單例、餓漢式單例、登記式單例三種。

  單例模式有一下特點(diǎn):

  1、單例類只能有一個(gè)實(shí)例。

  2、單例類必須自己自己創(chuàng)建自己的唯一實(shí)例。

  3、單例類必須給所有其他對(duì)象提供這一實(shí)例。

  單例模式確保某個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。在計(jì)算機(jī)系統(tǒng)中,線程池、緩存、日志對(duì)象、對(duì)話框、打印機(jī)、顯卡的驅(qū)動(dòng)程序?qū)ο蟪1辉O(shè)計(jì)成單例。這些應(yīng)用都或多或少具有資源管理器的功能。每臺(tái)計(jì)算機(jī)可以有若干個(gè)打印機(jī),但只能有一個(gè)Printer Spooler,以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)中。每臺(tái)計(jì)算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個(gè)通信端口同時(shí)被兩個(gè)請(qǐng)求同時(shí)調(diào)用??傊?,選擇單例模式就是為了避免不一致狀態(tài),避免政出多頭。

  正是由于這個(gè)特 點(diǎn),單例對(duì)象通常作為程序中的存放配置信息的載體,因?yàn)樗鼙WC其他對(duì)象讀到一致的信息。例如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息可能存放在數(shù)據(jù)庫(kù)或 文件中,這些配置數(shù)據(jù)由某個(gè)單例對(duì)象統(tǒng)一讀取,服務(wù)進(jìn)程中的其他對(duì)象如果要獲取這些配置信息,只需訪問該單例對(duì)象即可。這種方式極大地簡(jiǎn)化了在復(fù)雜環(huán)境 下,尤其是多線程環(huán)境下的配置管理,但是隨著應(yīng)用場(chǎng)景的不同,也可能帶來一些同步問題。

  三。典型例題

  首先看一個(gè)經(jīng)典的單例實(shí)現(xiàn)。

  publicclassSingleton { privatestaticSingleton uniqueInstance = null; privateSingleton() { // Exists only to defeat instantiation.} publicstaticSingleton getInstance() { if(uniqueInstance == null) { uniqueInstance = newSingleton(); } returnuniqueInstance; } // Other methods.。。}

  Singleton通過將構(gòu)造方法限定為private避免了類在外部被實(shí)例化,在同一個(gè)虛擬機(jī)范圍內(nèi),Singleton的唯一實(shí)例只能通過getInstance()方法訪問。(事實(shí)上,通過Java反射機(jī)制是能夠?qū)嵗瘶?gòu)造方法為private的類的,那基本上會(huì)使所有的Java單例實(shí)現(xiàn)失效。此問題在此處不做討論,姑且掩耳盜鈴地認(rèn)為反射機(jī)制不存在。)

  但是以上實(shí)現(xiàn)沒有考慮線程安全問題。所謂線程安全是指:如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的?;蛘哒f:一個(gè)類或者程序所提供的接口對(duì)于線程來說是原子操作或者多個(gè)線程之間的切換不會(huì)導(dǎo)致該接口的執(zhí)行結(jié)果存在二義性,也就是說我們不用考慮同步的問題。顯然以上實(shí)現(xiàn)并不滿足線程安全的要求,在并發(fā)環(huán)境下很可能出現(xiàn)多個(gè)Singleton實(shí)例。

  /** * Java學(xué)習(xí)交流QQ群:589809992 我們一起學(xué)Java! */publicclassTestStream{privateString name; publicString getName() { returnname; }publicvoidsetName(String name) { this.name = name; } //該類只能有一個(gè)實(shí)例privateTestStream(){} //私有無(wú)參構(gòu)造方法//該類必須自行創(chuàng)建//有2種方式/*private static final TestStream ts=new TestStream();*/privatestaticTestStream ts1= null; //這個(gè)類必須自動(dòng)向整個(gè)系統(tǒng)提供這個(gè)實(shí)例對(duì)象publicstaticTestStream getTest(){ if(ts1== null){ ts1=newTestStream(); } returnts1; } publicvoidgetInfo(){ System.out.println( “output message ”+name); } } publicclassTestMain { publicstaticvoidmain(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)建的是同一個(gè)實(shí)例”); } elseif(s!=s1){ System. out.println( “創(chuàng)建的不是同一個(gè)實(shí)例”); } else{ System. out.println( “application error”); } } }

  運(yùn)行結(jié)果:

  張孝祥 張孝祥 output message張孝祥 output message張孝祥 創(chuàng)建的是同一個(gè)實(shí)例

  結(jié)論:由結(jié)果可以得知單例模式為一個(gè)面向?qū)ο蟮膽?yīng)用程序提供了對(duì)象惟一的訪問點(diǎn),不管它實(shí)現(xiàn)何種功能,整個(gè)應(yīng)用程序都會(huì)同享一個(gè)實(shí)例對(duì)象。

  其次,下面是單例的三種實(shí)現(xiàn)。

  1.餓漢式單例類

  飛哥下面這個(gè)可以不加final,因?yàn)殪o態(tài)方法只在編譯期間執(zhí)行一次初始化,也就是只會(huì)有一個(gè)對(duì)象。

  //餓漢式單例類。在類初始化時(shí),已經(jīng)自行實(shí)例化 publicclassSingleton1 { //私有的默認(rèn)構(gòu)造子privateSingleton1() {} //已經(jīng)自行實(shí)例化 privatestaticfinal Singleton1 single =newSingleton1(); //靜態(tài)工廠方法 publicstaticSingleton1 getInstance() { returnsingle; } }

  2.懶漢式單例類

  那個(gè)if判斷確保對(duì)象只創(chuàng)建一次。

  //懶漢式單例類。在第一次調(diào)用的時(shí)候?qū)嵗?publicclassSingleton2 { //私有的默認(rèn)構(gòu)造子privateSingleton2() {} //注意,這里沒有final privatestaticSingleton2 single= null; //靜態(tài)工廠方法 publicsynchronized staticSingleton2 getInstance() { if(single == null) { single =newSingleton2(); } returnsingle; } }

  3.登記式單例類

  import java.util.HashMap; import java.util.Map; //登記式單例類。 Java學(xué)習(xí)交流QQ群:589809992 我們一起學(xué)Java!//類似Spring里面的方法,將類名注冊(cè),下次從里面直接獲取。publicclassSingleton3 { privatestaticMap《String,Singleton3》 map =newHashMap《String,Singleton3》(); static{ Singleton3 single = newSingleton3(); map.put(single.getClass().getName(), single); } //保護(hù)的默認(rèn)構(gòu)造子protectedSingleton3(){}//靜態(tài)工廠方法,返還此類惟一的實(shí)例publicstaticSingleton3 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(); } } returnmap. get(name); } //一個(gè)示意性的商業(yè)方法publicString about() { return“Hello, I am RegSingleton.”; }publicstaticvoidmain(String[] args) { Singleton3 single3 = Singleton3.getInstance( null); System. out.println(single3.about()); } }四。單例對(duì)象作配置信息管理時(shí)可能會(huì)帶來的幾個(gè)同步問題

  1.在多線程環(huán)境下,單例對(duì)象的同步問題主要體現(xiàn)在兩個(gè)方面,單例對(duì)象的初始化和單例對(duì)象的屬性更新。

  本文描述的方法有如下假設(shè):

  a. 單例對(duì)象的屬性(或成員變量)的獲取,是通過單例對(duì)象的初始化實(shí)現(xiàn)的。也就是說,在單例對(duì)象初始化時(shí),會(huì)從文件或數(shù)據(jù)庫(kù)中讀取最新的配置信息。

  b. 其他對(duì)象不能直接改變單例對(duì)象的屬性,單例對(duì)象屬性的變化來源于配置文件或配置數(shù)據(jù)庫(kù)數(shù)據(jù)的變化。

  1.1單例對(duì)象的初始化

  首先,討論一下單例對(duì)象的初始化同步。單例模式的通常處理方式是,在對(duì)象中有一個(gè)靜態(tài)成員變量,其類型就是單例類型本身;如果該變量為null,則創(chuàng)建該單例類型的對(duì)象,并將該變量指向這個(gè)對(duì)象;如果該變量不為null,則直接使用該變量。

  這種處理方式在單線程的模式下可以很好的運(yùn)行;但是在多線程模式下,可能產(chǎn)生問題。如果第一個(gè)線程發(fā)現(xiàn)成員變量為null,準(zhǔn)備創(chuàng)建對(duì)象;這是第二 個(gè)線程同時(shí)也發(fā)現(xiàn)成員變量為null,也會(huì)創(chuàng)建新對(duì)象。這就會(huì)造成在一個(gè)JVM中有多個(gè)單例類型的實(shí)例。如果這個(gè)單例類型的成員變量在運(yùn)行過程中變化,會(huì) 造成多個(gè)單例類型實(shí)例的不一致,產(chǎn)生一些很奇怪的現(xiàn)象。例如,某服務(wù)進(jìn)程通過檢查單例對(duì)象的某個(gè)屬性來停止多個(gè)線程服務(wù),如果存在多個(gè)單例對(duì)象的實(shí)例,就 會(huì)造成部分線程服務(wù)停止,部分線程服務(wù)不能停止的情況。

  1.2單例對(duì)象的屬性更新

  通常,為了實(shí)現(xiàn)配置信息的實(shí)時(shí)更新,會(huì)有一個(gè)線程不停檢測(cè)配置文件或配置數(shù)據(jù)庫(kù)的內(nèi)容,一旦發(fā)現(xiàn)變化,就更新到單例對(duì)象的屬性中。在更新這些信 息的時(shí)候,很可能還會(huì)有其他線程正在讀取這些信息,造成意想不到的后果。還是以通過單例對(duì)象屬性停止線程服務(wù)為例,如果更新屬性時(shí)讀寫不同步,可能訪問該 屬性時(shí)這個(gè)屬性正好為空(null),程序就會(huì)拋出異常。

  下面是解決方法

  //單例對(duì)象的初始化同步publicclassGlobalConfig { privatestaticGlobalConfig instance =null; privateVector properties = null; privateGlobalConfig() { //Load configuration information from DB or file//Set values for properties} privatestaticsynchronizedvoidsyncInit() { if(instance == null) { instance = newGlobalConfig(); } }publicstaticGlobalConfig getInstance() { if(instance == null) { syncInit(); } returninstance; }publicVector getProperties() { returnproperties; } }

  這種處理方式雖然引入了同步代碼,但是因?yàn)檫@段同步代碼只會(huì)在最開始的時(shí)候執(zhí)行一次或多次,所以對(duì)整個(gè)系統(tǒng)的性能不會(huì)有影響。

  單例對(duì)象的屬性更新同步。

  參照讀者/寫者的處理方式,設(shè)置一個(gè)讀計(jì)數(shù)器,每次讀取配置信息前,將計(jì)數(shù)器加1,讀完后將計(jì)數(shù)器減1.只有在讀計(jì)數(shù)器為0時(shí),才能更新數(shù)據(jù),同時(shí)要阻塞所有讀屬性的調(diào)用。

  代碼如下:

  /** * Java學(xué)習(xí)交流QQ群:589809992 我們一起學(xué)Java! */publicclassGlobalConfig{privatestaticGlobalConfig instance; privateVector properties =null; privatebooleanisUpdating = false; privateintreadCount = 0; privateGlobalConfig() {//Load configuration information from DB or file//Set values for properties}privatestaticsynchronizedvoidsyncInit() { if(instance == null) { instance =newGlobalConfig(); } } publicstaticGlobalConfig getInstance() { if(instance== null) { syncInit(); } returninstance; } publicsynchronizedvoidupdate(String p_data) { syncUpdateIn(); //Update properties} privatesynchronizedvoidsyncUpdateIn() {while(readCount 》 0) { try{ wait(); } catch(Exception e) { } } }privatesynchronizedvoidsyncReadIn() { readCount++; }privatesynchronizedvoidsyncReadOut() { readCount--; notifyAll(); } publicVectorgetProperties() { syncReadIn(); //Process datasyncReadOut(); returnproperties; } }

  采用”影子實(shí)例”的辦法具體說,就是在更新屬性時(shí),直接生成另一個(gè)單例對(duì)象實(shí)例,這個(gè)新生成的單例對(duì)象實(shí)例將從數(shù)據(jù)庫(kù)或文件中讀取最新的配置信息;然后將這些配置信息直接賦值給舊單例對(duì)象的屬性。

  publicclassGlobalConfig { privatestaticGlobalConfig instance = null; privateVector properties = null; privateGlobalConfig() { //Load configuration information from DB or file//Set values for properties} privatestaticsynchronized voidsyncInit() { if(instance = null) { instance = newGlobalConfig(); } } publicstaticGlobalConfig getInstance() { if(instance =null) { syncInit(); } returninstance; } publicVector getProperties() { returnproperties; }publicvoidupdateProperties() { //Load updated configuration information by new a GlobalConfig objectGlobalConfig shadow = newGlobalConfig(); properties = shadow.getProperties(); } }

  注意:在更新方法中,通過生成新的GlobalConfig的實(shí)例,從文件或數(shù)據(jù)庫(kù)中得到最新配置信息,并存放到properties屬性中。上面兩個(gè)方法比較起來,第二個(gè)方法更好,首先,編程更簡(jiǎn)單;其次,沒有那么多的同步操作,對(duì)性能的影響也不大。

非常好我支持^.^

(0) 0%

不好我反對(duì)

(0) 0%

      發(fā)表評(píng)論

      用戶評(píng)論
      評(píng)價(jià):好評(píng)中評(píng)差評(píng)

      發(fā)表評(píng)論,獲取積分! 請(qǐng)遵守相關(guān)規(guī)定!

      ?