1 餓漢式
public class EagerSingleton {
static {
System.out.println("EagerSingleton 被加載");
}
private EagerSingleton(){} //私有化構造方法,限制直接構造,只能調用 getInstance() 方法獲取單例對象
private static final EagerSingleton eagerSingleton=new EagerSingleton(); // 私有化靜態 final成員,類加載直接生成單例對象,比較占用內存
public static EagerSingleton getInstance(){ //提供對外的公共api獲取單例對象
return eagerSingleton;
}
}
總結:餓漢式單例的特點:餓漢式在類創建的同時就實例化一個靜態對象出來,不管之后會不會使用這個單例,都會占據一定的內存,但是相應的,在第一次調用時速度也會更快,因為其資源已經初始化完成。
2 懶漢式
public class LazySingleton {
static {
System.out.println("LazySingleton 被加載");
}
private LazySingleton(){} //私有化構造方法,限制直接構造,只能調用 getInstance() 方法獲取單例對象
private static LazySingleton lazySingleton=null;//靜態域初始化為null,為的是需要時再創建,避免像餓漢式那樣占用內存
public static LazySingleton getInstance(){//提供對外的公共api獲取單例對象
if(lazySingleton==null){
synchronized (LazySingleton.class){ //在getInstance中做了兩次null檢查,確保了只有第一次調用單例的時候才會做同步,這樣也是線程安全的,同時避免了每次都同步的性能損耗
if(lazySingleton==null){
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
總結:有同步鎖的性能消耗
3 靜態內部類實現
public class IoDHSingleton {
static {
System.out.println("IoDHSingleton 被加載");
}
private IoDHSingleton(){} //私有化構造方法,限制直接構造,只能調用 getInstance() 方法獲取單例對象
public static IoDHSingleton getInstance(){//提供對外的公共api獲取單例對象
return HolderClass.ioDHSingleton; //當getInstance方法第一次被調用的時候,它第一次讀取HolderClass.ioDHSingleton,內部類HolderClass類得到初始化;而這個類在裝載並被初始化的時候,會初始化它的靜態域,從而創建ioDHSingleton 的實例,由於是靜態的域, 因此只會在虛擬機裝載類的時候初始化一次,並由虛擬機來保證它的線程安全性。
}
private static class HolderClass{
static {
System.out.println("HolderClass 被加載");
}
private static IoDHSingleton ioDHSingleton = new IoDHSingleton();
}
// 防止反序列化獲取多個對象的漏洞
private Object readResolve() throws ObjectStreamException {
return HolderClass.ioDHSingleton;
}
}
這個模式的優勢在於,getInstance方法並沒有被同步,並且只是執行一個域的訪問,因此延遲初始化並沒有增加任何訪問成本。
考慮反射:
由於在調用 SingletonHolder.instance 的時候,才會對單例進行初始化,而且通過反射,是不能從外部類獲取內部類的屬性的。
所以這種形式,很好的避免了反射入侵。
考慮多線程:
由於靜態內部類的特性,只有在其被第一次引用的時候才會被加載,所以可以保證其線程安全性。
總結:
優勢:兼顧了懶漢模式的內存優化(使用時才初始化)以及餓漢模式的安全性(不會被反射入侵)。
劣勢:需要兩個類去做到這一點,雖然不會創建靜態內部類的對象,但是其 Class 對象還是會被創建,而且是屬於永久帶的對象。