什么時(shí)候使用Singleton
Singleton指僅僅被實(shí)例化一次的類吮炕。Singleton通常用來代表那些本質(zhì)上唯一的系統(tǒng)組件歧蒋,比如文件系統(tǒng),窗口管理器偿曙,日歷等氮凝。Singleton的類會(huì)使客戶端測(cè)試變得異常困難,因?yàn)闊o法給Singleton替換模擬實(shí)現(xiàn)望忆,除非Singleton實(shí)現(xiàn)一個(gè)充當(dāng)其類型的接口罩阵。
Java類的實(shí)例化
按照是否調(diào)用類的構(gòu)造器,可以簡單的將類實(shí)例化的方法分為兩大類:通過構(gòu)造器實(shí)例化和不通過構(gòu)造器實(shí)例化启摄。下面以實(shí)例化 Windows10FileSystem
類為例進(jìn)行詳細(xì)說明稿壁。
Windows10FileSystem.java
public class Windows10FileSystem {
private String name;
private String description;
// use default constructor
// getter/setter method
}
這個(gè)文件系統(tǒng)十分的“簡陋”,只包括文件系統(tǒng)名稱和文件系統(tǒng)描述歉备。
通過構(gòu)造器實(shí)例化類
-
通過
new
關(guān)鍵字傅是。Windows10FileSystem fileSystem = new Windows10FileSystem();
-
通過
Class
的newInstance()
方法。Windows10FileSystem fileSystem = (Windows10FileSystem) Class.forName("Windows10FileSystem").newInstance();
注意:
forName
的參數(shù)必須是類的全限定名蕾羊,這里為了簡單使用類名喧笔。 -
通過
Constructor
的newInstance()
方法。Constructor<Windows10FileSystem> constructor = Windows10FileSystem.class.getConstructor(); Windows10FileSystem fileSystem = constructor.newInstance();
上述方法只適用于構(gòu)造器可被訪問的場(chǎng)景肚豺,如果構(gòu)造器為
private
溃斋,可以使用下面的方法訪問構(gòu)造器界拦。如果
Windows10FileSystem
的構(gòu)造器是私有(private
)的吸申,借助AccessibleObject.setAccessible(true)
可以調(diào)用私有的構(gòu)造器:Constructor<Windows10FileSystem> constructor = Windows10FileSystem.class.getDeclaredConstructor(); constructor.setAccessible(true); Windows10FileSystem fileSystem = constructor.newInstance();
不使用構(gòu)造器實(shí)例化類
-
通過
clone()
函數(shù)。首先
Windows10FileSystem
類需要實(shí)現(xiàn)clone()
方法:@Override public Windows10FileSystem clone() { Windows10FileSystem copyFileSystem = new Windows10FileSystem(); copyFileSystem.setDescription(this.description); copyFileSystem.setName(this.name); return copyFileSystem; }
Windows10FileSystem fileSystem = new Windows10FileSystem(); fileSystem.setName("clone"); fileSystem.setDescription("graphical operating system"); Windows10FileSystem cloneFileSystem = fileSystem.clone();
-
通過反序列化享甸。
假設(shè)保存對(duì)象的文件為:fileSystem.obj
ObjectInputStream in = new ObjectInputStream(new FileInputStream("fileSystem.obj")); Windows10FileSystem fileSystem = (Windows10FileSystem) in.readObject();
Singleton的實(shí)現(xiàn)
實(shí)現(xiàn)Singleton的思路
上一節(jié)我們已經(jīng)了解Java中實(shí)例化一個(gè)類的多種方法截碴,而Singleton的目標(biāo)就是要確保類僅僅只被實(shí)例化一次,為此我們需要控制類實(shí)例化的入口蛉威,或者控制入口方法的調(diào)用次數(shù)或者控制方法每次調(diào)用返回同一個(gè)對(duì)象日丹,確保一個(gè)類只被實(shí)例化一次。
-
構(gòu)造器私有化蚯嫌,降低類構(gòu)造器的可見范圍哲虾。
private Windows10FileSystem() {}
-
構(gòu)造器私有化雖然可以降低訪問范圍丙躏,但享有特權(quán)的客戶端可以借助
AccessibleObject.setAccessible(true)
方法,通過反射機(jī)制調(diào)用私有的構(gòu)造器束凑,為了抵御這種攻擊晒旅,需要修改私有構(gòu)造器,在構(gòu)造器第二次調(diào)用時(shí)拋出異常汪诉,阻止類被多次實(shí)例化废恋。private static AtomicBoolean FIRST_INSTANTIATION = new AtomicBoolean(true); private Windows10FileSystem() { if (FIRST_INSTANTIATION.get()) { FIRST_INSTANTIATION.compareAndSet(true,false); } else { throw new UnsupportedOperationException(); } }
注意:這里沒有考慮并發(fā)調(diào)用構(gòu)造器的問題,在Java中可以使用鎖或
synchronized
簡單的進(jìn)行方法同步 -
為了讓Singleton支持序列化扒寄,只實(shí)現(xiàn)
Serializable
接口是不夠的鱼鼓,為了維護(hù)并保證Singleton,所有實(shí)例域都必須是瞬時(shí)(transient)的该编,并提供一個(gè)readResolve()
方法迄本,該方法每次都返回同一個(gè)實(shí)例。private static Windows10FileSystem INSTANCE = new Windows10FileSystem(); private Object readResolve() { return INSTANCE; }
該方法防止攻擊的原理和簡單的模擬可以參考《Effective Java》中的第77條-對(duì)于實(shí)例控制课竣,枚舉類型優(yōu)先于readResolve
-
類不要實(shí)現(xiàn)
Cloneable
接口和clone()
方法岸梨。保證不可以調(diào)用對(duì)象的
clone()
函數(shù)來實(shí)例化。
實(shí)現(xiàn)Singleton的三種方法
有多種方法可以實(shí)現(xiàn)Singleton稠氮,雖然每種方法的具體細(xì)節(jié)不一樣曹阔,但是每種方法的目標(biāo)都是相同的:確保類只被實(shí)例化一次。強(qiáng)烈推薦下文描述的第一種方法來實(shí)現(xiàn)Singleton:使用單元素枚舉類型實(shí)現(xiàn)Singleton隔披。
單元素枚舉類型實(shí)現(xiàn)Singleton
Java從1.5發(fā)型版本開始支持通過枚舉(enum
)實(shí)現(xiàn)Singleton赃份,下面以enum
實(shí)現(xiàn)一個(gè)Windows 10文件系統(tǒng),這個(gè)文件系統(tǒng)非常的簡陋奢米,只提供了名字和描述兩項(xiàng)基本信息抓韩,具體的代碼如下:
public enum Windows10FileSystem {
/**
* singleton file system instance
*/
INSTANCE("Windows 10", "graphical operating system");
private String name;
private String description;
Windows10FileSystem(String name, String separator) {
this.name = name;
this.description = separator;
}
public String getBaseInfo() {
return name + "\t" + description;
}
}
使用enum
實(shí)現(xiàn)Singleton更加簡潔,無償提供了序列化機(jī)制鬓长,絕對(duì)防止多次實(shí)例化谒拴,即使是在面對(duì)復(fù)雜的序列化和反射攻擊時(shí),這種方法依然絕對(duì)可靠涉波。強(qiáng)烈推薦使用該方法實(shí)現(xiàn)Singleton英上。
導(dǎo)出公有靜態(tài)成員(final 域)實(shí)現(xiàn)Singleton
public class Windows10FileSystem implements Serializable {
private static final AtomicBoolean FIRST_INSTANTIATION = new AtomicBoolean(true);
public static final Windows10FileSystem INSTANCE = new Windows10FileSystem("Windows 10","graphical operating system");
transient private String name;
transient private String description;
/**
* 構(gòu)造器私有化,并防止多次調(diào)用
* @param name 文件系統(tǒng)名稱
* @param separator 文件系統(tǒng)描述
*/
private Windows10FileSystem(String name, String separator) {
if (FIRST_INSTANTIATION.get()) {
FIRST_INSTANTIATION.compareAndSet(true,false);
this.name = name;
this.description = separator;
} else {
throw new UnsupportedOperationException("windows file system can only be instantiated once");
}
}
/**
* 防止反系列化攻擊
* @return file system object
*/
private Object readResolve() {
return INSTANCE;
}
public String getBaseInfo() {
return name + "\t" + description;
}
}
static變量的初始化順序參考JLS 8.7
公有靜態(tài)工廠方法實(shí)現(xiàn)Singleton
這種方法相比與導(dǎo)出公有靜態(tài)成員(final 域)實(shí)現(xiàn)Singleton而言只是公有靜態(tài)變量變成了一個(gè)工廠方法啤覆,每次調(diào)用工廠方法都返回同一個(gè)實(shí)例苍日。
private static final Windows10FileSystem INSTANCE = new Windows10FileSystem("Windows 10","graphical operating system");
public static Windows10FileSystem getInstance() {
return INSTANCE;
}
兩種實(shí)現(xiàn)Singleton方法的核心都是通過私有化構(gòu)造器來控制類的實(shí)例化。公有域方法的主要優(yōu)勢(shì)在于窗声,類的成員聲明很清楚的表明這個(gè)類是一個(gè)Singleton(可讀性強(qiáng)):公有的靜態(tài)域是final的相恃,所以該域?qū)⒖偸前嗤膶?duì)象。公有域在性能上已經(jīng)不再擁有任何優(yōu)勢(shì)笨觅,現(xiàn)代化的JVM實(shí)現(xiàn)幾乎都能將靜態(tài)工廠方法的調(diào)用內(nèi)聯(lián)化拦耐。靜態(tài)工廠方法的優(yōu)勢(shì)在于耕腾,它提供了更高的靈活性:在不改變API的條件下,我們可以改變?cè)擃愂欠袷荢ingleton的想法杀糯。工廠方法返回該類的唯一實(shí)例幽邓,但是,這可以很容易的被修改火脉,比如修改為每一個(gè)線程返回同一個(gè)實(shí)例牵舵。