本人參考了:Balan的文章 Java Singleton 實(shí)用教程(附源碼)
原文地址:http://balan.javaeye.com/blog/164873
一、定義
單例模式(Singleton pattern):確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局的訪問點(diǎn)。
這個(gè)定義包含兩層意思:
第一:我們把某個(gè)類設(shè)計(jì)成自己管理的一個(gè)單獨(dú)實(shí)例,同時(shí)也要避免其他類再自行產(chǎn)生實(shí)例。要想取得單個(gè)實(shí)例,通過單例類是唯一的途徑。
第二:我們必需提供對這個(gè)實(shí)例的全局訪問點(diǎn):當(dāng)你需要實(shí)例時(shí),向類查詢,它會給你返回單個(gè)實(shí)例。
注意:單例模式確保一個(gè)類只有一個(gè)實(shí)例,是指在特定系統(tǒng)范圍內(nèi)只能有一個(gè)實(shí)例。有時(shí)在某些情況下,使用Singleton并不能達(dá)到Singleton的目的,如有多個(gè)Singleton對象同時(shí)被不同的類裝入器裝載;在EJB這樣的分布式系統(tǒng)中使用也要注意這種情況,因?yàn)镋JB是跨服務(wù)器,跨JVM的。
1。某個(gè)框架容器內(nèi):如Spring IOC容器,可以通過配置保證實(shí)例在容器內(nèi)的唯一性。
2。再如單一JVM中、單一類加載器加載類的情況可以保證實(shí)例的唯一性。
如果在兩個(gè)類加載器或JVM中,可能他們有機(jī)會各自創(chuàng)建自己的單個(gè)實(shí)例,因?yàn)槊總€(gè)類加載器都定義了一個(gè)命名空間,如果有兩個(gè)以上的類加載器,不同的類加載器可能會加載同一個(gè)類,從整個(gè)程序來看,同一個(gè)類會被加載多次。如果這樣的事情發(fā)生在單例上,就會產(chǎn)后多個(gè)Singleton并存的怪異現(xiàn)象。所以如果你的程序有多個(gè)類加載,同時(shí)你又使用了單例模式,請一定要小心。有一個(gè)解決辦法是,自行給單例類指定類加載器(指定同一個(gè)類加載器)。
二、用處
有一些對象其實(shí)我們完全只需要一個(gè)即可,如:線程池(threadpool)、緩存(cache)、注冊表(registry)的對象、設(shè)備的驅(qū)動(dòng)程序的對象等等。事實(shí)上,這些類的對象只能有一個(gè)實(shí)例,如果制造出多個(gè)實(shí)例,就會導(dǎo)致許多問題的產(chǎn)生,例如:程序的行為異常、資源的過量使用、產(chǎn)生不一致的結(jié)果等等。Java Singleton模式就為我們提供了這樣實(shí)現(xiàn)的可能。使用Singleton的好處還在于可以節(jié)省內(nèi)存,因?yàn)樗拗屏藢?shí)例的個(gè)數(shù),有利于Java垃圾回收(garbage collection)。我們常??吹焦S模式中類裝入器(class loader)中也用Singleton模式實(shí)現(xiàn)的,因?yàn)楸谎b入的類實(shí)際也屬于資源。
三、Java Singleton模式常見的幾種形式
一)。使用立即創(chuàng)建實(shí)例,而不用延遲實(shí)例化的做法
1。使用全局變量
//Singleton with final fieldpublic class Singleton {public static final Singleton uniqueInstance = new Singleton();private Singleton(){}//...Remainder omitted}
在這種方法中,公有靜態(tài)成員是一個(gè)final域(保證了總是包含相同的對象引用)。私有構(gòu)造函數(shù)僅被調(diào)用一次,用來實(shí)例化公有的靜態(tài)final域 Singleton.uniqueInstace。由于缺少公有的或者受保護(hù)的構(gòu)造函數(shù),所有保證了Singleton的全局唯一性:一旦 Singleton類被實(shí)例化之后,只有一個(gè)Singleton實(shí)例存在——不多也不少。使用此Singleton類的程序員的任何行為都不能改變這一點(diǎn)。
2。使用公有的靜態(tài)工廠方法
//Singleton with static factorypublic class Singleton {private static Singleton uniqueInstance = new Singleton();private Singleton(){}public static Singleton getInSingleton(){return uniqueInstance;}//...Remainder omitted}
第二種方法提供了一個(gè)公有的靜態(tài)工廠方法,而不是公有的靜態(tài)final域。利用這個(gè)做法,我們依賴JVM在加載這個(gè)類時(shí)馬上創(chuàng)建此類唯一的一個(gè)實(shí)例。JVM保證任何線程訪問uniqueInstance靜態(tài)變量之前,一定先創(chuàng)建此實(shí)例。
二)。使用延遲實(shí)例化的做法(使用公有的靜態(tài)工廠方法)
1。非線程安全的
public class Singleton {private static Singleton uniqueInstance ;private Singleton(){}public static Singleton getInSingleton(){if(uniqueInstance == null){uniqueInstance = new Singleton();}return uniqueInstance;}//...Remainder omitted}
先利用一個(gè)靜態(tài)變量uniqueInstance來記錄Singleton類的唯一實(shí)例,當(dāng)我們要使用它的實(shí)例時(shí),如果它不存在,就利用私有的構(gòu)造器產(chǎn)生一個(gè)Singleton類的實(shí)例并把它賦值到uniqueInstance靜態(tài)變量中。而如果我們不需要使用這個(gè)實(shí)例,它就永遠(yuǎn)不會產(chǎn)生。這就是"延遲實(shí)例化(lazy instantiaze)"。但上面這段程序在多線程環(huán)境中是不能保證單個(gè)實(shí)例的。分析如下:
時(shí)間點(diǎn) | 線程1 | 線程2 | uniqueInstance的值 |
1 | 線程1,2同時(shí)訪問Singleton.getInstance()方法 | ||
2 | 進(jìn)入Singleton.getInstance()方法 | null | |
3 | 進(jìn)入Singleton.getInstance()方法 | null | |
4 | 執(zhí)行if(uniqueInstance == null)判斷 | null | |
5 | 執(zhí)行if(uniqueInstance == null)判斷 | null | |
6 | 執(zhí)行uniqueInstance = new Singleton() | Singleton1 | |
7 | 執(zhí)行uniqueInstance = new Singleton() | Singleton2 | |
8 | 執(zhí)行return uniqueInstance; | Singleton1 | |
9 | 執(zhí)行return uniqueInstance; | Singleton2 |
如上分析所示,它已產(chǎn)生了兩個(gè)Singleton實(shí)例。
2。多線程安全的
public class Singleton {private static Singleton uniqueInstance ;private Singleton(){}public synchronized static Singleton getInSingleton(){if(uniqueInstance == null){uniqueInstance = new Singleton();}return uniqueInstance;}//...Remainder omitted}
通過給getInstance()方法增加synchronized關(guān)鍵字,也就是給getInstance()方法線程加鎖,迫使每次只能有一個(gè)線程在進(jìn)入這個(gè)方法,這樣就可以解決上面多線程產(chǎn)生的災(zāi)難了。但加鎖的同步方法可能造成程序執(zhí)行效率大幅度下降,如果你的程序?qū)π阅艿囊蠛芨?,同時(shí)你的getInstance()方法調(diào)用的很頻繁,這時(shí)可能這種設(shè)計(jì)也不符合程序要求了。其實(shí)這種加鎖同步的方法用在這確實(shí)有一定的問題存在,因?yàn)閷?Singleton類來說,只有在第一次執(zhí)行g(shù)etInstance()方法時(shí),才真正的需要對方法進(jìn)行加鎖同步,因?yàn)橐坏┑谝淮卧O(shè)置好 uniqueInstance變量后,就不再需要同步這個(gè)方法了。之后每次調(diào)用這個(gè)方法,同步反而成了一種累贅。
3。 用"雙重檢查加鎖",在getInstance()方法中減少使用同步:
public class Singleton {// volatile關(guān)鍵字確保當(dāng)uniqueInstance變量被初始化成Singleton實(shí)例時(shí),多個(gè)線程正確地處理uniqueInstance變量private volatile static Singleton uniqueInstance;private Singleton() {}public static Singleton getInSingleton() {if (uniqueInstance == null) {// 檢查實(shí)例,如是不存在就進(jìn)行同步代碼區(qū)synchronized (Singleton.class) {// 對其進(jìn)行鎖,防止兩個(gè)線程同時(shí)進(jìn)入同步代碼區(qū)if (uniqueInstance == null) {// 雙重檢查,非常重要,如果兩個(gè)同時(shí)訪問的線程,當(dāng)?shù)谝痪€程訪問完同步代碼區(qū)后,生成一個(gè)實(shí)例;當(dāng)?shù)诙€(gè)已進(jìn)入getInstance方法等待的線程進(jìn)入同步代碼區(qū)時(shí),也會產(chǎn)生一個(gè)新的實(shí)例uniqueInstance = new Singleton();}}}return uniqueInstance;}// ...Remainder omitted}
對于雙重檢查加鎖(Double-Checked Locking)有一篇文章解釋的很深入:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
三。Sington類的序列化
為了使Singleton類變成可序列化的(serializable),僅僅實(shí)現(xiàn)Serializable接口是不夠的。為了維護(hù) Singleton的單例性,你必須給Singleton類提供一個(gè)readResolve方法,否則的話,一個(gè)序列化的實(shí)例,每次反序列化的時(shí)候都會產(chǎn)生一個(gè)新的實(shí)例。Singleton 也不會例外。
如下所示:
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.ObjectStreamException;import java.io.Serializable;//Singleton with final fieldpublic class Singleton implements Serializable{private static final long serialVersionUID = 5765648836796281035L;public static final Singleton uniqueInstance = new Singleton();private Singleton(){}//...Remainder omittedpublic static void main(String[] args) throws Exception{//序列化ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\Singleton.obj"));Singleton singleton = Singleton.uniqueInstance;objectOutputStream.writeObject(singleton);objectOutputStream.close();//反序列化ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\Singleton.obj"));Singleton singleton2 = (Singleton)objectInputStream.readObject();objectInputStream.close();//比較是否原來的實(shí)例System.out.println(singleton==singleton2);}}
輸出結(jié)果為:false
解決方法是為Singleton類增加readResolve()方法:
//readResolve 方法維持了Singleton的單例屬性private Object readResolve() throws ObjectStreamException{return uniqueInstance;}
再進(jìn)行測試:輸出結(jié)果為true