單例模式的8種寫法
阿新 • • 發佈:2020-12-10
1、餓漢式,執行緒安全
public class Singleton { /** * 優點:寫法簡單,類裝載的時候完成例項化,執行緒安全 * 缺點:類裝載的時候就完成例項化,沒有Lazy Loading,如果沒有被使用,會造成記憶體浪費 */ private static final Singleton INSTANCE = new Singleton(); public static Singleton getInstance(){ return INSTANCE; } }
2、餓漢式,執行緒安全
public class Singleton { /** * 與第一種方式類似,只是將例項化過程放在靜態塊中。不知道算不算一種寫法。 * 優點:寫法簡單,類裝載的時候完成例項化,執行緒安全 * 缺點:類裝載的時候就完成例項化,沒有Lazy Loading,如果沒有被使用,會造成記憶體浪費 */ private static Singleton INSTANCE; static{ if(INSTANCE == null){ INSTANCE = new Singleton(); } } public static Singleton getInstance(){ return INSTANCE; } }
3、懶漢式,執行緒不安全
public class Singleton { /** * 優點:Lazy Loading。避免多餘的記憶體開銷。 * 缺點:執行緒不安全,多執行緒環境下容易產生多個例項。 */ private static Singleton INSTANCE; public static Singleton getInstance(){ if(INSTANCE == null){ INSTANCE = new Singleton(); } return INSTANCE; } }
4、懶漢式,執行緒安全(同步方法)
public class Singleton {
/**
* 優點:Lazy Loading。避免多餘的記憶體開銷。synchronized執行緒同步,保證了執行緒安全。
* 缺點:synchronized將方法序列化,效率太低
*/
private static Singleton INSTANCE;
public static synchronized Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
5、懶漢式,執行緒安全(同步程式碼塊)
public class Singleton {
/**
* 優點:Lazy Loading。避免多餘的記憶體開銷。改良同步方法效率低的問題。
* 缺點:synchronized同步程式碼塊,無法保證執行緒同步。如果,執行緒A在判斷INSTANCE == null還未執行完畢,另一個執行緒已經return出一個例項,那麼將會產生多個例項。
*/
private static Singleton INSTANCE;
public static Singleton getInstance(){
if(INSTANCE == null){
synchronized(Singleton.class){
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
6、懶漢式,執行緒安全(雙重檢查)
public class Singleton {
/**
* 優點:執行緒安全,Lazy Loading,效率高
* 缺點:使用volatile,jdk1.5版本之前無法完全避免重排序鎖導致的問題。jdk1.5之前無法保證執行緒安全。(現在都java12了, 問題不大)
*/
private static volatile Singleton INSTANCE;
public static Singleton getInstance(){
if(INSTANCE == null){
synchronized(Singleton.class){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
7、靜態內部類,執行緒安全
public class Singleton {
/**
* 優點:Lazy Loading,執行緒安全,寫法簡單
* 缺點:需要額外處理序列化問題,否則一個序列化的物件例項在每次反序列化後都會建立一個新的例項;傳參複雜。
*/
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
8、列舉,執行緒安全(推薦使用)
public enum Singleton {
/**
* 執行緒安全,避免反序列化問題。推薦使用
*/
INSTANCE;
public void getInstance(){
}
// public static void main(String[] args) {
// Singleton.INSTANCE.getInstance();
// }
}
為什麼說列舉能保證執行緒安全,而且能保證單例?
通過javap反編譯Singleton列舉。
INSTANCE最終被宣告為static final。
JVM類載入過程中,虛擬機器會保證一個類的<clinit>()方法在多執行緒環境中被正確地加鎖、同步,如果多個執行緒同時去初始化一個類,那麼只會有一個執行緒去執行這個類的<clinit>()方法,其他執行緒都需要阻塞等待,直到活動執行緒執行<clinit>()方法完畢。如果在一個類的<clinit>()方法中有耗時很長的操作,就可能造成多個程序阻塞(需要注意的是,其他執行緒雖然會被阻塞,但如果執行<clinit>()方法後,其他執行緒喚醒之後不會再次進入<clinit>()方法。同一個載入器下,一個型別只會初始化一次。),在實際應用中,這種阻塞往往是很隱蔽的。
所以列舉既保證了執行緒的安全性,又保證了例項的單例性。
總結:單例模式使用哪種寫法,最終取決於業務的需要(如是否需要頻繁的建立或者銷燬物件)。推薦雙重檢查,靜態內部類,列舉三種寫法。