java中的設計模式-單例模式
阿新 • • 發佈:2018-12-22
java 面試中單例模式基本都是必考的,都有最推薦的方式,也不知道問來幹嘛。下面記錄一下
餓漢式(也不知道為何叫這個名字)
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
其實真心覺得沒什麼問題
private Singleton
來修飾可以防止建立多個例項- 沒有延遲載入?這是需求不同好嗎!有很多的需求是希望一開始就載入好的,不希望要用的時候再載入的,比如是
java.lang.Runtime
但面試的時候,你不能這樣回答,面試官不開心的,你要回答
- 沒有延遲載入
- 譬如 Singleton 例項的建立是依賴引數或者配置檔案的,在 getInstance() 之前必須呼叫某個方法設定引數給它,是不能使用。(在建構函式上傳參不行嗎?,不考慮延遲載入)
懶漢模式
public class Singleton { private volatile static Singleton instance; //宣告成 volatile private Singleton (){} public static Singleton getSingleton() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
你看加個延遲載入
- 要將 instance 宣告個 volatile 好讓在多執行緒的環境下可見
- 又要注意在多執行緒的情況下要主要加鎖,因為會出現一個執行緒認為是空要構造物件,而另一個物件也認為是空要構造物件是情況
麻煩!!!
但也有應用場景的,在資源佔用很多,又不常用的情況下,可以考慮用懶漢模式
懶漢式二式 —— 內部靜態類
public class Singleton { private final static class SingleHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingleHolder.INSTANCE; } }
因為這個是 JVM 保證其線性安全性的,而且會在載入的時候建立,又不依賴版本,所以以前會比較推薦。
致命的危險
其實上面的東東都有致命的危險!反射
上面按正常人類的做法是不會產生多個例項,如果會產生多個例項是說明上面的方式或多或少不夠完美 ,比如反射
@Test
public void test() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Constructor<?> cls = Singleton.class.getDeclaredConstructor();
cls.setAccessible(true);
Singleton singleton = (Singleton) cls.newInstance();
assertNotEquals(singleton,Singleton.getInstance());//明顯這兩個物件是不一樣的!!!
}
也就是說上面的方式面對複雜的序列化或者反射攻擊,可能會出現問題!(當然要先實現 Serializable 介面)
比如:
@Test
public void test() throws ClassNotFoundException, IOException {
//序列化物件到檔案
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("singleton"));
objectOutputStream.writeObject(Singleton.getInstance());
//從檔案中讀取物件
File file = new File("singleton");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
Singleton singleton = (Singleton) objectInputStream.readObject();
//判斷是否是同一個物件
assertNotEquals(singleton,Singleton.getInstance());
}
當然也有應對序列化的方式。
就是在類中新增readResolve
函式,因為反序列化中,如果存在這個函式,序列化的結果就是這函式的值。
所以你在要序列化中的類新增這句就可以了。
private Object readResolve() {
return getInstance();
}
但面對反射就確實無力了。
最好的方式 - 列舉類
public enum SingletonEnum {
INSTANCE;
}
- 面對反射:jvm 直接禁止了通過反射構造列舉例項的行為!
- 面對序列化:jvm 對 enum 類的序列化,不是呼叫 writeObject、readObject 這些方法的。結果是就算是序列化後再反序列化,結果都是一樣。
以上