單例設計模式

常見的五種單例模式實現方式– 主要:

  • 餓漢式(線程安全,調用效率高。 但是,不能延時加載。)
  • 懶漢式(線程安全,調用效率不高。 但是,可以延時加載。)
  • 其他:

    • 雙重檢測鎖式(由於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;
    
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章