寫在最前面
單例的文章很多,所以本文主要想記錄下幾種常用單例的不好理解之處
- 雙重檢測 靜態資源上的volatile 關鍵字作用 ?
- 靜態內部類怎麼實現線程安全單例 ?
- 枚舉單例怎麼理解 ?
單例的概念
-
單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。通過單例模式可以保證系統中,應用該模式的一個類只有一個實例。即一個類只有一個對象實例。
-
Java中單例模式定義:“一個類有且僅有一個實例,並且自行實例化向整個系統提供。”
詳情見百度百科
單例的特點
- 一是某個類只能有一個實例
- 二是它必須自行創建這個實例
- 三是它必須自行向整個系統提供這個實例
詳情見百度百科
單例的幾種寫法
- 餓漢模式 (線程安全 低效)
/**
* 類加載時就創建實例
*/
public class Singleton {
private Singleton(){}
private final static Singleton singleton= new Singleton();
public static Singleton getInstance(){
return singleton;
}
}
/**
* 靜態代碼塊實現(類加載時)
*/
public class Singleton {
private Singleton() {}
private static Singleton singleton;
static {
singleton= new Singleton();
}
public static Singleton getInstance() {
return singleton;
}
}
- 懶漢模式 (線程不安全)
public class Singleton {
private Singleton(){}
private static Singleton singleton;
public static Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
- 懶漢加鎖 (線程安全 低效)
/**
* 方法加鎖
*/
public class Singleton {
private Singleton() {}
private static Singleton singleton;
public static synchronized Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
/**
* 代碼塊加鎖
*/
public class Singleton {
private Singleton() {}
private static Singleton singleton;
public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
-
雙重檢測 (線程安全 高效)
創建實例代碼 singleton = new Singleton(); 主要做了三件事 (1.開闢空間 2.創建對象 3.地址指向) 1> 不加volatile:可能執行順序爲132 這時第二個獲取資源會是地址不爲空但是對象實例爲空 2> 加上volatile:防止JVM指令重排 確保執行順序爲123 確保要麼靜態變量爲NULL 要麼有地址指向並且對象實例也不爲空
public class Singleton {
private Singleton() {}
private static volatile Singleton singleton;
public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
if (null == singleton) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
-
靜態內部類 (線程安全 高效)
靜態內部類是怎麼保證線程安全的呢? 虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確地加鎖、同步,如果多個線程同時去初始化一個類,那麼只會有 一個線程去執行這個類的<clinit>()方法,其他線程都需要阻塞等待,直到活動線程執行<clinit>()方法完畢。如果在一個類的 <clinit>()方法中有耗時很長的操作,就可能造成多個進程阻塞(需要注意的是,其他線程雖然會被阻塞,但如果執行<clinit>() 方法後,其他線程喚醒之後不會再次進入<clinit>()方法。同一個加載器下,一個類型只會初始化一次。) 在實際應用中,這種阻塞往往是很隱蔽的
public class Singleton {
private Singleton() {}
private static class SingletonInner {
private static final Singleton singleton= new Singleton();
}
public static Singleton getInstance() {
return SingletonInner.singleton;
}
}
-
枚舉 (線程安全 高效)
枚舉實現單例可能不太好理解 其實在編譯成class文件時,會默認爲枚舉類成員加上 final static修飾 再想想就和靜態內部類的實現幾乎一樣了 在實際代碼中可以將需要執行一次的可寫在私有方法中
public enum Singleton {
INSTANCE;
public void methodA() {
}
// 枚舉中私有方法只會執行一次
private void methodB() {
}
}