四個不同單例模式寫法的Java源代碼
- 懶漢模式
- 餓漢模式
- 懶漢模式 + 安全線程
- 雙重檢驗鎖
第一個懶漢模式線程不安全,后三個都是線程安全的。四者的共有特點,也是單例模式的最主要特點,是其**構造函數是私有的**。還有一個特點是,有一個**靜態**的getInstance() 方法,靜態方法就是類方法。
# 懶漢模式
這個寫法是GoF提出單例模式時候給出的例子,影響力大,寫法簡單。
~~~
package singleton;
// 懶漢模式
public class SingletonLazy
{
private static SingletonLazy uniqueInstance;
private SingletonLazy(){} // 私有的構造函數
public static SingletonLazy getInstance()
{
if(uniqueInstance == null)
{
uniqueInstance = new SingletonLazy();
}
return uniqueInstance;
}
public String toString()
{
return "A simple way to apply Singleton, but is not thread safe";
}
}
~~~
然而問題在于:當有多于一個線程的時候,懶漢模式可能失效。舉個例子:
1. 線程A和線程B,相隔0.1納秒分別執行getInstance()方法,A先于B
1. 時刻”T 納秒”, A發現 uniqueInstance==null,準備開始new SingletonLazy( )
1. 時刻”T+0.1 納秒”, **A還沒來得及new完SingletonLazy對象**,**此時B發現 uniqueInstance==null**,也準備開始new SingletonLazy( )
1. 時刻”T+0.5 納秒”, A成功new完了SingletonLazy對象,uniqueInstance!=null
1. 時刻”T+0.6 納秒”, B成功new完了SingletonLazy對象
可以看出A,B兩個線程都new了SingletonLazy對象,懶漢模式失效。原因在于:A和B兩個線程相隔非常非常短的時間分別執行getInstance(),而且new SingletonLazy對象這個過程需要花費一定的時間。
# 餓漢模式
餓漢模式寫法,是一上來(類加載的時候)就給你實例一個Singleton對象,不管你此時需不需要。回顧一下懶漢模式寫法,你一開始不需要Singleton對象,然后程序運行到某一時刻,第一次調用getInstance()方法,才實例一個Singleton對象。餓漢模式的寫法,由JVM保證是安全的(雖然內部機制我不懂,我才剛開始學Java),不過簡單想一想,Singleton類加載之前,肯定不會有線程new Singleton(),此時Singleton()的構造函數還不存在呢~
可以這么說,餓漢模式解決線程安全問題的方法是:從根子上回避這個問題。想法很好,寫法很簡單,不過呢要多花費一些空間(犧牲空間,換取時間,這個世界就是很難有兩全其美的事情)
~~~
package singleton;
// 餓漢模式
public class SingletonEager
{
private static SingletonEager uniqueInstance = new SingletonEager(); // 在這里 new
private SingletonEager(){} // 私有的構造函數
public static SingletonEager getInstance()
{
return uniqueInstance;
}
public String toString()
{
return "Create the unique instance when the class is loaded, which is thread safe";
}
}
~~~
# 懶漢模式(線程安全)
在懶漢模式的基礎上,在getInstance() 方法的聲明中,增加關鍵詞synchronized,就可以實現線程安全了。畢竟同步嘛,線程A和B即使時間相隔非常非常短,比如相隔0.1納秒,那也是分先后呀。就因為A快上0.1納秒,所以就”捷足先登“了,拿到了鎖!B在0.1納秒后,發現getInstance()方法上了鎖,進不去了。
~~~
package singleton;
public class SingletonThreadSafe
{
private static SingletonThreadSafe uniqueInstance;
private SingletonThreadSafe(){} // 私有的構造函數
// 這里同步了
public static synchronized SingletonThreadSafe getInstance()
{
if(uniqueInstance == null)
{
uniqueInstance = new SingletonThreadSafe();
}
return uniqueInstance;
}
public String toString()
{
return "The getInstance() method is declared with keyword 'synchronized',"
+ " which is thread safe, but with low performance";
}
}
~~~
不過《Head First Design Pattern》說:Just keep in mind that synchronizing a method can decrease performance by a factor of 100。相差100倍,這對于程序性能的影響是相當的大呀!
# 雙重檢驗鎖
這個是上面的synchronized方法的升級版本。仔細想一想,只有在第一次getInstance()的時候,才需要new singleton對象,對吧?如果不是第一次getInstance(),那就說明singleton對象已經存在了~于是有了下面的優化代碼
~~~
package singleton;
// double checked locking
public class SingletonDCL
{ // 注意這個關鍵詞
private volatile static SingletonDCL uniqueInstance;
private SingletonDCL(){} // 私有的構造函數
public static SingletonDCL getInstance()
{
if(uniqueInstance == null) // check once
{
synchronized(SingletonDCL.class)
{
if(uniqueInstance == null) // check twice
{
uniqueInstance = new SingletonDCL();
}
}
}
return uniqueInstance;
}
public String toString()
{
return "A thread safe way to apply Singleton with good performance";
}
}
~~~
synchronized的不是一個方法,而是一個方法里面的一個代碼塊,這樣被synchronized的部分減少了。
- 注意1:synchronized前后分別check兩次。第一個check,Singleton是否曾經被實例化過;第二個check,就相當于上一個例子”懶漢模式+線程安全“中的check
- 注意2:volatile關鍵詞,這涉及到JVM內部的機制,先強行記住就行了。
# 測試及補充
~~~
package singleton;
public class Main
{
public static void main(String[] args)
{
SingletonLazy singletonLazy = SingletonLazy.getInstance();
SingletonLazy singletonLazy2 = SingletonLazy.getInstance();
System.out.println(singletonLazy);
if(singletonLazy2.equals(singletonLazy))
{
System.out.println("true"); // 同一個引用
}
else
{
System.out.println("false");
}
}
}
~~~
運行結果:
~~~
A simple way to apply Singleton, but is not thread safe
true
~~~
**補充:**
《Head First Design Pattern》書中單例模式就這4種寫法。不過其實還有更多寫法,在實驗樓網站中,就還有靜態內部類寫法和枚舉類型寫法。《Head First Design Pattern》在GitHub的代碼中,有一個例子為了讓單例模式能夠派生出子類,把構造函數和靜態數據成員聲明為protected(子類訪問權限)。
深入單例模式以及其他設計模式,[猛戳這里(可以在新標簽頁中打開~)](http://blog.csdn.net/u013390476/article/details/50333763)
[某大神博客,C++版本的單例模式,我覺得寫的不錯](http://blog.csdn.net/fu_zk/article/details/11892095)
[這個鏈接好,C++博大精深。。。](http://segmentfault.com/q/1010000000593968)