一、定義
單例模式(Singleton pattern):確定一個類隻有一個執行個體,并提供一個全局的通路點。
這個定義包含兩層意思:
第一:我們把某個類設計成自己管理的一個單獨執行個體,同時也要避免其他類再自行産生執行個體。要想取得單個執行個體,通過單例類是唯一的途徑。
第二:我們必需提供對這個執行個體的全局通路點:當你需要執行個體時,向類查詢,它會給你傳回單個執行個體。
注意:單例模式確定一個類隻有一個執行個體,是指在特定系統範圍内隻能有一個執行個體。有時在某些情況下,使用Singleton并不能達到Singleton的目的,如有多個Singleton對象同時被不同的類裝入器裝載;在EJB這樣的分布式系統中使用也要注意這種情況,因為EJB是跨伺服器,跨JVM的。
1。某個架構容器内:如Spring IOC容器,可以通過配置保證執行個體在容器内的唯一性。
2。再如單一JVM中、單一類加載器加載類的情況可以保證執行個體的唯一性。
如果在兩個類加載器或JVM中,可能他們有機會各自建立自己的單個執行個體,因為每個類加載器都定義了一個命名空間,如果有兩個以上的類加載器,不同的 類加載器可能會加載同一個類,從整個程式來看,同一個類會被加載多次。如果這樣的事情發生在單例上,就會産後多個Singleton并存的怪異現象。是以如果你的程式有 多個類加載,同時你又使用了單例模式,請一定要小心。有一個解決加法是,自行給單例類指定類加載器(指定同一個類加載器)。
二、用處
有一些對象其實我們完全隻需要一個即可,如:線程池(threadpool)、緩存(cache)、系統資料庫(registry)的對象、裝置的驅動程式的對象等等。事實上,這些類的對象隻能有一個執行個體,如果制造出多個執行個體,就會導緻許多問題的産生,例如:程式的行為異常、資源的過量使用、産生不一緻的結果等等。Java Singleton模式就為我們提供了這樣實作的可能。使用Singleton的好處還在于可以節省記憶體,因為它限制了執行個體的個數,有利于Java垃圾回收(garbage collection)。我們常常看到工廠模式中類裝入器(class loader)中也用Singleton模式實作的,因為被裝入的類實際也屬于資源。
三、Java Singleton模式常見的幾種形式
一)。使用立即建立執行個體,而不用延遲執行個體化的做法
1。使用全局變量
//Singleton with final field
public class Singleton {
public static final Singleton uniqueInstance = new Singleton();
private Singleton(){
}
//...Remainder omitted
}
在這種方法中,公有靜态成員是一個final域(保證了總是包含相同的對象引用)。私有構造函數僅被調用一次,用來執行個體化公有的靜态final域Singleton.uniqueInstace。由于缺少公有的或者受保護的構造函數,所有保證了Singleton的全局唯一性:一旦Singleton類被執行個體化之後,隻有一個Singleton執行個體存在——不多也不少。使用此Singleton類的客戶的任何行為都不能改變這一點。
2。使用公有的靜态工廠方法
//Singleton with static factory
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return uniqueInstance;
}
//...Remainder omitted
}
第二種方法提供了一個公有的靜态工廠方法,而不是公有的靜态final域。利用這個做法,我們依賴JVM在加載這個類時馬上建立此類唯一的一個執行個體。JVM保證任何線程通路uniqueInstance靜态變量之前,一定先建立此執行個體。
二)。使用延遲執行個體化的做法(使用公有的靜态工廠方法)
1。非線程安全的
public class Singleton {
private static Singleton uniqueInstance ;
private Singleton(){
}
public static Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
//...Remainder omitted
}
先利用一個靜态變量uniqueInstance來記錄Singleton類的唯一執行個體,當我們要使用它的執行個體時,如果它不存在,就利用私有的構造器産生一個Singleton類的執行個體并把它指派到uniqueInstance靜态變量中。而如果我們不需要使用這個執行個體,它就永遠不會産生。這就是"延遲執行個體化(lazy instantiaze)"。但上面這段程式在多線程環境中是不能保證單個執行個體的。分析如下:
時間點 | 線程1 | 線程2 | uniqueInstance的值 |
1 | 線程1,2同時通路Singleton.getInstance()方法 | ||
2 | 進入Singleton.getInstance()方法 | null | |
3 | 進入Singleton.getInstance()方法 | null | |
4 | 執行if(uniqueInstance == null)判斷 | null | |
5 | 執行if(uniqueInstance == null)判斷 | null | |
6 | 執行uniqueInstance = new Singleton() | Singleton1 | |
7 | 執行uniqueInstance = new Singleton() | Singleton2 | |
8 | 執行return uniqueInstance; | Singleton1 | |
9 | 執行return uniqueInstance; | Singleton2 |
如上分析所示,它已産生了兩個Singleton執行個體。
2。多線程安全的
public class Singleton {
private static Singleton uniqueInstance ;
private Singleton(){
}
public synchronized static Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
//...Remainder omitted
}
通過給getInstance()方法增加synchronized關鍵字,也就是給getInstance()方法線程加鎖,迫使每次隻能有一個線程在進入這個方法,這樣就可以解決上面多線程産生的災難了。但加鎖的同步方法可能造成程式執行效率大幅度下降,如果你的程式對性能的要求很高,同時你的getInstance()方法調用的很頻繁,這時可能這種設計也不符合程式要求了。其實這種加鎖同步的方法用在這确實有一定的問題存在,因為對Singleton類來說,隻有在第一次執行getInstance()方法時,才真正的需要對方法進行加鎖同步,因為一旦第一次設定好uniqueInstance變量後,就不再需要同步這個方法了。之後每次調用這個方法,同步反而成了一種累贅。
3。 用"雙重檢查加鎖",在getInstance()方法中減少使用同步:
public class Singleton {
// volatile關鍵字確定當uniqueInstance變量被初始化成Singleton執行個體時,多個線程正确地處理uniqueInstance變量
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {// 檢查執行個體,如是不存在就進行同步代碼區
synchronized (Singleton.class) {// 對其進行鎖,防止兩個線程同時進入同步代碼區
if (uniqueInstance == null) {// 雙重檢查,非常重要,如果兩個同時通路的線程,當第一線程通路完同步代碼區後,生成一個執行個體;當第二個已進入getInstance方法等待的線程進入同步代碼區時,也會産生一個新的執行個體
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
// ...Remainder omitted
}
三。Sington類的序列化
為了使Singleton類變成可序列化的(serializable),僅僅實作Serializable接口是不夠的。為了維護Singleton的單例性,你必須給Singleton類提供一個readResolve方法,否則的話,一個序列化的執行個體,每次反序列化的時候都會産生一個新的執行個體。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 field
public class Singleton implements Serializable{
private static final long serialVersionUID = 5765648836796281035L;
public static final Singleton uniqueInstance = new Singleton();
private Singleton(){
}
//...Remainder omitted
public 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();
//比較是否原來的執行個體
System.out.println(singleton==singleton2);
}
}
輸出結果為:false
解決方法是為Singleton類增加readResolve()方法:
//readResolve 方法維持了Singleton的單例屬性
private Object readResolve() throws ObjectStreamException{
return uniqueInstance;
}
再進行測試:輸出結果為true
反序列化之後新建立的對象會先調用此方法,該方法傳回的對象引用被傳回,取代了新建立的對象。本質上,該方法忽略了建立對象,仍然傳回類初始化時建立的那個執行個體。