單例是應(yīng)用開發(fā)中一種設(shè)計(jì)模式只磷,主要應(yīng)用場(chǎng)景為:當(dāng)且僅當(dāng)系統(tǒng)中只能保留一個(gè)對(duì)象時(shí)使用抱完。本文提出4中可以在生產(chǎn)環(huán)境中使用的單例設(shè)計(jì)模式狗热。推薦使用enum的方式卖宠。
應(yīng)用場(chǎng)景
例如一下應(yīng)用場(chǎng)景[1]
:
1滤灯、 Windows的Task Manager(任務(wù)管理器)就是很典型的單例模式(這個(gè)很熟悉吧)坪稽,想想看,是不是呢鳞骤,你能打開兩個(gè)windows task manager嗎窒百?
2、網(wǎng)站的瀏覽人數(shù)統(tǒng)計(jì)豫尽,一般也是采用單例模式實(shí)現(xiàn)篙梢,否則難以同步。
3美旧、應(yīng)用程序的日志應(yīng)用渤滞,一般都何用單例模式實(shí)現(xiàn),這一般是由于共享的日志文件一直處于打開狀態(tài)榴嗅,因?yàn)橹荒苡幸粋€(gè)實(shí)例去操作妄呕,否則內(nèi)容不好追加。
//todo
在joshua block 的《effective java second edition》 一書中給出了三種單例設(shè)計(jì)模式
1嗽测、采用靜態(tài)變量:
public class TaskManager {
public static final TaskManager INSTANCE = new TaskManager ();
private TaskManager (){}
//...
}
這種寫法使用了私有的構(gòu)造方法绪励。來保證只能有一個(gè)實(shí)例,但是這種方法也有例外情況,因?yàn)檫胫啵憧梢酝ㄟ^反射來調(diào)用私有構(gòu)造方法疏魏。這個(gè)時(shí)候你可以拋出異常。以下代碼僅作為參考厅贪。
public class TaskManager {
public static final TaskManager INSTANCE = new TaskManager();
private TaskManager() {
if (INSTANCE != null) {
try {
throw new Exception("An object already exists");
} catch (Exception e) {
e.printStackTrace();
}
}
}
//...
}
2蠢护、采用靜態(tài)方法
public class TaskManager {
private static final TaskManager INSTANCE = new TaskManager();
private TaskManager() {}
public static TaskManager getINSTANCE() {
return INSTANCE;
}
//...
}
3、采用enum的方式
這種模式是目前最佳的养涮,因?yàn)椋?br>
1葵硕、JVM會(huì)保證enum不能被反射并且構(gòu)造器方法只執(zhí)行一次眉抬。
2、此方法無償提供了序列化機(jī)制懈凹,絕對(duì)防止反序列化時(shí)多次實(shí)例化蜀变。
3、運(yùn)行時(shí)(compile-time )創(chuàng)建對(duì)象(懶加載) // todo 關(guān)于cmpile-time和run-time有時(shí)間我單獨(dú)寫一篇文章介评。
enum是jdk5的特性库北,現(xiàn)在(2017)web應(yīng)用普遍在jdk6、7们陆、8寒瓦,所以可以放心使用。
目前最佳的方式是使用接口的方式(解耦):
interface Resource {
Object doSomething();
}
public enum SomeThing implements Resource {
INSTANCE {
@Override
public Object doSomething() {
return "I am a Singleton nstance";
}
};
}
class Demo {
public static void main(String[] args) {
System.out.println(SomeThing.INSTANCE.doSomething());
}
}
或者不使用接口的形式
public enum SomeThing {
INSTANCE;
public void doSomething() {
System.out.println("INSTANCE = " + INSTANCE);
}
}
class Demo {
public static void main(String[] args) {
SomeThing.INSTANCE.doSomething();
}
}
也有人用其他的方式坪仇,我對(duì)這種方法持強(qiáng)烈反對(duì),具體可以參考文獻(xiàn)4杂腰,以下代碼僅做參考
class Resource {
}
public enum SomeThing {
INSTANCE;
private Resource instance;
SomeThing() {
instance = new Resource();
}
public Resource getInstance() {
return instance;
}
}
class Demo {
public static void main(String[] args) {
System.out.println(SomeThing.INSTANCE.getInstance());
}
}
在其他文章中有提到“懶漢”、“惡漢”的名詞椅文,其實(shí)懶漢主要就是"懶"加載[注:指在使用時(shí)裝載喂很,不使用時(shí)不進(jìn)行裝載]
有人提出這種懶漢設(shè)計(jì)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
很顯然這種設(shè)計(jì)線程不安全皆刺,一般不會(huì)使用。
有人又提出了懶漢改進(jìn)的方法羡蛾,使其線程安全。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
這種寫法能夠在多線程中很好的工作林说,而且看起來它也具備很好的lazy loading煎殷,但是,遺憾的是,因?yàn)槭侵亓考?jí)鎖腿箩,效率很低。
于是有人提出了雙重校驗(yàn)鎖機(jī)制,這個(gè)用的也比較多珠移。
下面代碼就是用double checked locking 方法實(shí)現(xiàn)的單例,這里的getInstance()方法要檢查兩次钧惧,確保是否實(shí)例INSTANCE是否為null或者已經(jīng)實(shí)例化了暇韧,這也是為什么叫double checked locking 模式。
/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
參考文獻(xiàn):
[1] Jason Cai, 設(shè)計(jì)模式之——單例模式(Singleton)的常見應(yīng)用場(chǎng)景
[2] cantellow, 單例模式的七種寫法
[3] Javarevisited, 單例模式中為什么用枚舉更好
[4] natsumi, Java枚舉實(shí)現(xiàn)單例模式
[5] zejian_浓瞪,深入理解Java枚舉類型(enum)