常見的五種單例模式實現方式– 主要:
- 餓漢式(線程安全,調用效率高。 但是,不能延時加載。)
- 懶漢式(線程安全,調用效率不高。 但是,可以延時加載。)
-
其他:
- 雙重檢測鎖式(由於JVM底層內部模型原因,偶爾會出問題。不建議使用)
- 靜態內部類式(線程安全,調用效率高。 但是,可以延時加載)
- 枚舉式(線程安全,調用效率高,不能延時加載。並且可以天然的防止反射和反序列化漏洞!)
-
如何選用?
-
單例對象 佔用 資源 少,不需要 延時加載
- 枚舉式 好於 餓漢式
-
單例對象 佔用 資源 大,需要 延時加載:
- 靜態內部類式 好於 懶
-
餓漢
public class OneObject {
private static final OneObject OBJECT = new OneObject();
private OneObject () {}
public static OneObject build () {
return OBJECT;
}
}
懶漢
public class OneObjectLaz {
private static OneObjectLaz laz;
private OneObjectLaz () {}
public static OneObjectLaz build () {
synchronized (OneObjectLaz.class) {
if (null == laz) laz = new OneObjectLaz();
}
return laz;
}
}
多線程測試
for (int i = 0; i < 10; i++) {
new Thread(() -> {
OneObjectLaz build = OneObjectLaz.build();
System.out.println(build.hashCode());
}).start();
}
通過反射機制暴力範圍 private 構造器, 從而達到多實例
Constructor<OneObject> constructor = OneObject.class.getDeclaredConstructor();
constructor.setAccessible(true);
OneObject newInstance = constructor.newInstance();
System.out.println(newInstance);
解決方案
public class OneObjectLaz {
private static OneObjectLaz laz;
private OneObjectLaz () {
if (laz != null) throw new RuntimeException("對象必須保證單例!");
}
public static OneObjectLaz build () {
synchronized (OneObjectLaz.class) {
if (null == laz) laz = new OneObjectLaz();
}
return laz;
}
}
防止序列化破解單例
public class OneObjectLaz {
private static OneObjectLaz laz;
private OneObjectLaz () {
if (laz != null) throw new RuntimeException("對象必須保證單例!");
}
public static OneObjectLaz build () {
synchronized (OneObjectLaz.class) {
if (null == laz) laz = new OneObjectLaz();
}
return laz;
}
/**
* 防止單例, 反序列化破解
* 如果定義了: readResolve 方法在反序列時直接返回方法指定的對象, 不需要new了
*/
private Object readResolve() throws ObjectStreamException {
return laz;
}
}
要想真正的實現單利漏洞還是很多的所以
jdk
提供了新特性:enum
public enum One {
ONE, TWO, THREE;
}