單例模式三種初始化方法及其優缺點
一、常見單例模式實現方式
單例模式:顧名思義即確保一個類只有一個實例,而且自行實例化(私有構造方法)並向整個系統提供這個實例。
常見的單例模式有三種模式:懶漢式單例、餓漢式單例、靜態內部類單例(也叫登記式單例、holder單例)。
1.餓漢式單例
實現方式:
public class Singleton {
private static final Singleton SINGLETON = new Singleton();
private Singleton{}; // 該類不能被實例化
public static Singleton getInstance() {
return SINGLETON;
}
}
優點:沒有加鎖,執行效率會提高。
缺點:類加載時就初始化,浪費內存。
2.懶漢式單例(也叫飽漢式單例)
實現方式一同步方法:
public class Singleton {
private static Singleton singleton;
private Singleton{}; // 該類不能被實例化
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
實現方式二同步代碼塊(雙重檢查鎖定方式,比方式一好):
public class Singleton {
private static Singleton singleton;
private Singleton{}; // 該類不能被實例化
public static Singleton getInstance() {
if (singleton == null) {
synchronized(Singleton.class) {
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
優點:第一次調用才初始化,避免內存浪費。
缺點:必須加鎖synchronized 才能保證單例,即需要考慮線程同步,否則多個線程同時調用getInstance方法,有可能回出現實例化多次,但加鎖會在一定程度上影響效率。
3.靜態內部類單例
實現方式:
public class Singleton {
private static Singleton singleton;
private Singleton{}; // 該類不能被實例化
public static Singleton getInstance() {
return Holeder.SINGLETON;
}
private static class Holder {
private final static Singleton SINGLETON = new Singleton();
}
}
優點:1)內部類只有在外部類被調用才加載,產生SINGLETON實例;2)不用加鎖。
缺點:代碼稍微複雜。
4.避免反射入侵方法
Java反射機制是指在運行狀態中,對於任意一個類都能夠知道這個類所有的屬性和方法;並且對於任意一個對象,都能夠調用它的任意一個方法。
單例模式在常規情況下,只能通過 getInstance() 創建實例;但是通過反射也可以直接調用private方法創建實例,即可以多次調用私有構造方法,產生反射入侵的問題。
避免反射入侵方法思路是避免單例類的構造方法被多次調用,可以通過改在私有構造方法和getInstance方法實現,下面的實現以內部靜態類模式爲例:
public class Singleton {
private static boolean sFlag = true;
private static Singleton singleton;
private Singleton{
if (sFlag) {
sFlag = fase;
} else { // 防止被實例化多次
throw new RuntimeException("單例模式被攻擊了");
}
}; // 該類不能被實例化
public static Singleton getInstance() {
return Holeder.SINGLETON;
}
private static class Holder {
private final static Singleton SINGLETON = new Singleton();
}
}
二、高效單例模式實現——枚舉類型單例模式
上面介紹的三種單例實現方式,是比較常見的方式,下面介紹一種更簡單而且高效的單例模式——枚舉類型單例模式。
實現方式一:構造方法中實例化對象
// 枚舉單例模式定義
public enum Singleton {
INSTANCE;
public Singleton getInstance() {
return INSTANCE;
}
public void method1() {
}
}
// 枚舉單例使用
Singleton.INSTANCE.method1();
// 枚舉單例模式定義
public class EnumSingleton {
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private EnumSingleton singleton;
//JVM會保證此方法絕對只調用一次
Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return singleton;
}
}
}
// 枚舉單例使用
EnumSingleton.getInstance().method1();
實現方式二:枚舉常量的值即爲對象實例
public class EnumSingleton {
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
// 枚舉常量的值即爲對象實例
INSTANCE(new EnumSingleton());
private EnumSingleton singleton;
//JVM會保證此方法絕對只調用一次
Singleton(EnumSingleton singleton){
this.singleton = singleton;
}
public EnumSingleton getInstance(){
return singleton;
}
}
}
// 枚舉單例使用
EnumSingleton.getInstance().method1();
優點:
- 枚舉單例有序列化和線程安全的保證,而且JVM會保證枚舉類構造方法絕對只調用一次,所以保證了對象實例的唯一性;
- 枚舉ENUM類型,不允許反射調用,天生的避免了反射入侵(驚不驚喜,神不神奇!!!)。