單例模式的鼎鼎大名很早以前都聽說過了,數據庫的連接池就是采用了單例模式,但是一直不知道單例模式到底是什么,我當時想難道是只有一個實例嗎,那又怎么可能呢?隨便 new 一個不是又有很多實例嗎?
單例模式的定義:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
如何保證只有一個實例
看到單例模式的實現后有點好笑,原本以為是一個多么高大上的代碼技巧才能實現整個類只有一個實例,后來才發現原來這么簡單,只需要把構造函數設置為私有的。
public class Singleton
{
private static Singleton uniqueInstance;
private Singleton()
{
System.out.println("Initial");
}
public static Singleton getInstance()
{
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
看吧,就是這么簡單,構造函數設置為私有的,讓這個單例類不能夠隨便用 new 直接初始化一個實例了,并且設置一個靜態 Singleton 并且他也是私有的,他就是這個單例模式中的單例,只能通過自己創建的 getInstance 函數來訪問他,這就解決了使用 new 無限創建實例的問題。
懶漢式單例模式
但是上面的單例模式并不是線程安全的,假如兩個線程同時進入到if (uniqueInstance == null)
這行代碼,那么他們都會同時判斷此時 uniqueInstance 沒有實例化,那么就會都進入uniqueInstance = new Singleton();
這行代碼,也就意味著會把這個單例實例化兩次,這就違反了單例模式的原則,那么就需要把這個函數加上同步鎖
public class Singleton
{
private static Singleton uniqueInstance;
private Singleton()
{
System.out.println("Initial");
}
public synchronized static Singleton getInstance()
{
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
但是 synchronized 對于性能有較大的影響,畢竟我們只是第一次創建的時候才需要關注線程安全。
餓漢式單例模式
還有一種辦法可以就解決線程安全問題:
public class Singleton
{
private static Singleton uniqueInstance=new Singleton();
private Singleton()
{
System.out.println("Initial");
}
public synchronized static Singleton getInstance()
{
return uniqueInstance;
}
}
這種方法的話就完全不同擔心線程同步的問題了,但是卻不能做到用時才創建的特性了。那么有沒有一種方式既能有很好的性能又能做到用時才創建的特性呢?
雙重檢查加鎖
public class Singleton
{
private volatile static Singleton uniqueInstance=new Singleton();
private Singleton()
{
System.out.println("Initial");
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
看到這里也許會問,為什么要加兩次if (uniqueInstance == null)
檢查呢?我們用排除法來分析一下。
- 如果不加第一個
if (uniqueInstance == null)
,那么每次進入函數都會遇到 synchronized 鎖,這就又導致了性能的損耗,和我們前面的懶漢式單例沒有什么區別了。 - 如果不加第二個
if (uniqueInstance == null)
,那么兩個線程同時進入第一個if (uniqueInstance == null)
時,如果這時單例模式的第一次創建,那么由于兩個線程同時進入也就會同時判斷uniqueInstance == null
為true,那么接下來排隊進入鎖區,沒有第二個if (uniqueInstance == null)
控制的話,那么uniqueInstance = new Singleton();
會被兩個線程同時執行兩次。這就又違反了單例模式的原則。
同時需要注意成員變量 uniqueInstance 一定要設置為 volatile ,多個線程才能正確地處理 uniqueInstance 變量。
總結
單例模式確保程序中一個類最多只有一個實例,并提供訪問這個實例的全局訪問點,確定在性能上和資源上的限制,正確的選擇不同單例模式的實現以解決多線程的問題。