在我們平時工作 或者 面試的時候,單例模式算是出現最頻繁的一種設計模式了,本文整理了單例模式的各種實現
1、餓漢模式【簡而言之,就是不管你用不用,我先創建出來】
public class HungrySingleton {
private static final HungrySingleton hungry = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungry;
}
}
還有另一種寫法,利用靜態代碼塊的機制來實現
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungry;
static {
hungry = new HungryStaticSingleton();
}
private HungryStaticSingleton() {
}
public static HungryStaticSingleton getInstance() {
return hungry;
}
}
優點:沒有鎖,執行效率比較高,用戶體驗比懶漢模式要好
缺點:浪費內存【用不用都會先創建】
2、懶漢模式【用的時候再創建實例】
public class LazySimpleSingleton {
public LazySimpleSingleton() {}
private static LazySimpleSingleton lazy = null;
public static LazySimpleSingleton getInstance() {
if (lazy == null) {
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
上面的懶漢模式是我們最常見的,但再多線程環境下可能創建多個實例,有興趣的可以自己試一下。
解決多線程環境下的安全隱患,最先想到的就是加鎖,如下所示:
public class LazySimpleSingleton {
public LazySimpleSingleton() {}
private static LazySimpleSingleton lazy = null;
public static synchronized LazySimpleSingleton getInstance() {
if (lazy == null) {
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
這種方式雖然解決了多線程的安全問題,但是當線程比較多的時候,大批線程會阻塞,CUP的壓力也會上升。
那麼我們怎麼能解決這種情況呢,可以使用雙重檢查鎖的方式來解決,如下所示:
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton() {}
public static LazyDoubleCheckSingleton getInstance() {
if (lazy == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazy == null) {
lazy = new LazyDoubleCheckSingleton();
}
}
}
return lazy;
}
}
以上情況會有很大的改觀,但用到鎖,終歸還是有一定的影響。有什麼更好的方法呢?可以考慮採用靜態內部類的方式來解決。
3、靜態內部類
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton() {}
// 每一個關鍵字都不是多餘的,static 是爲了使單例的空間共享,保證這個方法不會被重寫、重載
public static final LazyInnerClassSingleton getInstance() {
// 在返回結果之前一定會加載內部類
return lazyHolder.LAZY;
}
// 靜態內部類 默認不加載
private static class lazyHolder {
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
這種方法可以很好的兼顧餓漢單例模式的內存浪費問題和 synchronized 的性能問題,完美的屏蔽了這兩個缺點。
4、反射破壞單例
上面的形式在大部分情況下都是沒有問題的,但是如果使用反射來創建對象的,我們發現仍然是可以創建兩個實例
public class Test {
public static void main(String[] args) {
// 利用反射則會破壞內部類單例模式
try {
Class<?> clazz = LazyInnerClassSingleton.class;
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Object o1 = c.newInstance();
Object o2 = c.newInstance();
System.out.println(o1 == o2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
返回結果
false
顯然,創建了兩個不同的實例,我們可以在構造函數中加一些限制,一旦重複創建就拋出異常或者指定的信息。如下所示:
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton() {
// 此構造函數中做限制,保證反射不會被反射破壞【使用序列化會創建多個實例】
if (lazyHolder.LAZY != null) {
throw new RuntimeException("不允許創建多個實例");
}
}
// 每一個關鍵字都不是多餘的,static 是爲了使單例的空間共享,保證這個方法不會被重寫、重載
public static final LazyInnerClassSingleton getInstance() {
// 在返回結果之前一定會加載內部類
return lazyHolder.LAZY;
}
// 靜態內部類 默認不加載
private static class lazyHolder {
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
同樣使用上方的多線程進行測試,運行代碼,控制檯結果如下:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.xiang.designPattern.singleton.Test.main(Test.java:26)
Caused by: java.lang.RuntimeException: 不允許創建多個實例
at com.xiang.designPattern.singleton.LazyInnerClassSingleton.<init>(LazyInnerClassSingleton.java:14)
... 5 more
自此,上書所寫的單例模式基本可以滿足所有的情況,謹以此文記之。
如有更好的實現方式,還請不吝賜教!!!