什麼是單例模式?
1、一個類僅生成一個執行個體,作為大家共有的資源。
<span style="font-size: 18px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">2、單例模式限制類的執行個體化和保證類隻有一個執行個體存在于Java虛拟機。單例類必須提供獲得該類的執行個體的全局通路點。單例模式用于測井,驅動對象,緩存和線程池。</span>
3、Singleton設計模式也被用在其他的設計模式,抽象工廠,生成器,原型,立面等單件設計模式是用于核心Java類也,例如java.lang.runtime,java.awt.desktop。
單例模式實作主要的幾種方式?
單例模式的實作方式有很多,主要的實作如下:
1、餓漢式
2、懶漢式
3、靜态初始化方式(餓漢加強)
4、枚舉方式
5、雙重鎖定(懶漢加強)
6、靜态内部類的方式
餓漢式實作
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
//private constructor to avoid client applications to use constructor
private EagerInitializedSingleton(){}
public static EagerInitializedSingleton getInstance(){
return instance;
}
}
餓漢式單例模式,在系統初始化的時候就加載單例對象
缺點:1、如果單例為資源或者檔案系統,我們應該避免在用戶端調用getInstance方法前就執行個體化。(若執行個體話)
2、沒有提供任何的異常處理的選項。
靜态初始化單例(餓漢式加強)
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
private StaticBlockSingleton(){}
//static block initialization for exception handling
static{
try{
instance = new StaticBlockSingleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static StaticBlockSingleton getInstance(){
return instance;
}
}
說明:靜态初始化單例,是餓漢式單例模式的加強。通過在靜态初始化塊中進行,擁有了異常捕獲的選擇。
缺點:如餓漢式一樣,系統一初始化就加載單例。
懶漢式實作
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton() {
}
public static LazyInitializedSingleton getInstance() {
if (instance == null) {
instance = new LazyInitializedSingleton();
}
return instance;
}
}
說明:懶漢式單例模式,單例在初始化時不進行執行個體,等有用戶端調用getInstance方法時,判斷是否有執行個體,沒有建立執行個體,若存在執行個體則傳回該執行個體對象。
缺點:1、适合在單線程中使用該方式,若存在多線程環境的話,會造成多單例的情況。
雙重校驗鎖(懶漢式加強)
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
if(instance == null){
synchronized (ThreadSafeSingleton.class) {
if(instance == null){
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
說明:該方式是在懶漢式的基礎上進行多線程優化的實作。
缺點:由于是在懶漢式的基礎上做的,加強了多線程的操作。将多線程同步操作的同時。每次都要避免這種額外的開銷,雙重檢查鎖定原理應用。
在這種方法中,同步塊内用如果確定建立隻有一個單例類執行個體的一個額外的檢查條件。
枚舉實作
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
//do something
}
}
<span style="font-family: Arial, Helvetica, sans-serif; font-size: 14px; background-color: rgb(255, 255, 255);">分析:這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新建立新的對象,</span>
可謂是很堅強的壁壘啊,不過,個人認為由于1.5中才加入enum特性。
靜态内部類實作
public class BillPughSingleton {
private BillPughSingleton(){}
private static class SingletonHelper{
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
說明:該方式通過内部類的方式實作單例的。
優勢:1、由于試用了内部類的方式,不試用的時候不進行加載。
2、jvm在初始化靜态變量的時候僅初始化一次,是以線程安全。
通過反射的方式破壞單例
public class ReflectionSingletonTest {
public static void main(String[] args) {
EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
EagerInitializedSingleton instanceTwo = null;
try {
Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
//Below code will destroy the singleton pattern
constructor.setAccessible(true);
instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(instanceOne.hashCode());
System.out.println(instanceTwo.hashCode());
}
}
方式:
1、通過反射的方式獲得類類型
Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
2、 将private類型構造方法設定為可操作的。
constructor.setAccessible(true);
3、創造單例的執行個體對象
instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
面對反射的破壞,如何抵禦?
如果借助AccessibleObject.setAccessible方法,通過反射機制調用私有構造器,抵禦這種方法可以在建立第二次構造器時抛出異常來解決。
//自定義異常
public class InitializationException extends RuntimeException {
private static final long serialVersionUID = 1L;
public InitializationException(String msg) {
super(msg);
}
}
public class Singleton {
private static int count = 0;
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
if(count == 1)
throw new InitializationException("隻能初始化一次!");
count++;
}
public static Singleton getInstance() {
return INSTANCE;
}
}
測試類如下:
public class Test {
public static void main(String[] args) throws Exception{
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // 傳回 true
// 試圖通過反射機制建立執行個體
for(Constructor<?> c : s1.getClass().getDeclaredConstructors()) {
c.setAccessible(true); // AccessibleObject
Singleton s3 = (Singleton)c.newInstance();
}
}
}
測試結果:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at org.reflect.Test.main(Test.java:17)
Caused by: org.reflect.InitializationException: 隻能初始化一次!
at org.reflect.Singleton.<init>(Singleton.java:10)
... 5 more
當嘗試反射的方式再一次初始化時,由于計數器在判斷第二次進入構造方法是進行判斷,計數器數量大于1則抛出自定義異常。
public class SingletonSerializedTest {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SerializedSingleton instanceOne = SerializedSingleton.getInstance();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
"filename.ser"));
out.writeObject(instanceOne);
out.close();
//deserailize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream(
"filename.ser"));
SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
in.close();
System.out.println("instanceOne hashCode="+instanceOne.hashCode());
System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
}
}
方式說明
由于實作了Serializable接口,通過将資料流的方式重新将生成一個新的單例對象。
解決方法方式:
在單例類中實作readResolve方法。
protected Object readResolve() {
return getInstance();
}
原因:在實作反序列化的時候,ObjectInputStream 會檢查對象的class是否定義了readResolve方法。如果定義了,将由readResolve方法指定傳回的對象。