單例模式是最簡單的設計模式之一,提供了一種建立對象的方式,確保在整個系統中只有一個對象被建立.單例模式解決了頻繁建立重複對象的問題節約資源,能夠省略建立對象所須要花費的時間,對於一些重量級對象而言這點是很重要的.而且由於不須要頻繁建立對象 GC 的壓力也會有所減輕.java
一般來講在 Java 中的單例模式分爲餓漢式和懶漢式,並且單例類須要一個 private 的構造函數防止被其餘代碼實例化.下面來具體說一下java 中單例模式的實現.設計模式
public class Singleton{
private static Singleton instance =new Singleton();
//私有化構造方法
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
複製代碼
餓漢式的單例模式代碼簡單,線程安全.先建立對象,而後等待調用首先私有化構造方法,防止別人使用new 建立對象.經過classLoader機制保證了單例對象的惟一性 可是不能確保instance 是在調用getInstance()方法的時候生成的不能達到懶加載效果安全
public class Singleton{
private static Singleton instance;
private Singleton(){}
//加入 synchronize 保證線程安全
public synchronized static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
複製代碼
爲了達到懶加載的效果,咱們使用懶漢式的單例模式,在第一次調用方法getInstance()的時候纔去建立對象.能夠達到延遲加載的效果而且加入了 synchronize 保證線程安全,但每次調用代碼的時候都要加鎖,性能比較低還有可能發生阻塞jvm
public class Singleton{
//volatile防止指令重排序
private static volatile Singleton instance=null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
//加入第二次校驗
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
複製代碼
雙重校驗鎖就是爲了解決上述問題而存在的,先檢查實例是否存在而後再去建立,能夠不用每次調用方法都獲取同步鎖性能會有一些提高,減少的鎖的顆粒度.可是 java 對象的建立和賦值不是一步操做的,有可能先去賦值給中instance以後纔去建立 Singleton 這時添加volatile關鍵字防止指令重排序解決了這個問題.函數
對象建立的過程:性能
在代碼的第 12 行 instance =new Singleton();大體分爲三個過程 1.分配對象的內存空間 此時 instance !=nullspa
2.初始化對象線程
3.將instance 指向分配的內存空間設計
其中2 和 3不必定是有序的 因此線程 B 會訪問到一個還未初始化的對象code
public class Singleton{
private Singleton(){}
public static class SingleHoler{
public static final Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingleHoler.instance;
}
}
複製代碼
看過繁瑣的DCL後 下面介紹一種簡潔的單例模式靜態內部類.當Singleton被建立的時候不會去加載SingleHoler,只有第一次調用getInstance()方法時纔回去建立instance,加載SingleHoler將常量池中的符號引用替換成直接引用,這種方式不只保證了線程安全並且能夠達到延遲加載的效果.
classload機制
解決重排序的方法有兩種,第一種就是使用 volatile ,第二種則是如今要介紹的方法
調用類的靜態成員(非字符串常量)的時候會致使類(SingleHoler)的初始化.而且在執行類的初始化期間,JVM 會獲取一個初始化鎖,這個鎖能夠同步多個線程對同一個類的初始化.
類加載的步驟:
將符號引用替換成直接引用是在解析的階段完成的.
public enum Singleton{
INSTANCE;
public void print(){
System.out.println("快樂就完事了!");
}
}
複製代碼
這種方法在功能上與公有域方法相近,可是它更加簡潔,無償提供了序列化機制,絕對防止屢次實例化,即便是在面對複雜序列化或者反射攻擊的時候。雖然這種方法尚未普遍採用,可是單元素的枚舉類型已經成爲實現Singleton的最佳方法。 --《Effective Java 中文版 第二版》
簡單到不能再簡單了啊.jvm 在加載枚舉類的時候會使用loadClass方法使用同步代碼塊解決線程安全問題.使用 enum 的單例模式還能避免反序列化破壞單例而且不能被反射攻擊.