1. 定義
優點
缺點
特性:
(1)私有構造函數
(2)線程安全
(3)延遲加載
(4)序列化和反序列化
(5)反射攻擊
2. 懶漢模式
多線程創建:
主函數直接調用
開啓線程調試:類型設置爲Thread
開始調試:
thread0
thread1
直接往下走,生成兩個對象。
3. 雙重檢驗鎖
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazySingleton = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance(){
if(lazySingleton==null){//1.第一次檢查,此處是爲了減少性能開銷,不用每次進入synchronized中
synchronized (LazyDoubleCheckSingleton.class) {
if(lazySingleton==null){//2.第二次檢測
/*問題:原來的排序過程是:1、2、3,但是可能jvm指令重排序成爲1、3、2,
* 此時可能兩個線程都在執行2,執行3的時候這兩個線程都new了LazyDoubleCheckSingleton對象
*
* 解決辦法:volatile
* */
//1.分配內存給這個對象
//3.設置lazyDoubleCheckSingleton 指向剛分配的內存地址
//2.new一個LazyDoubleCheckSingleton對象
lazySingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazySingleton;
}
}
4. 靜態內部類
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
private StaticInnerClassSingleton(){
}
}
靜態內部類在外部類被調用時不會立即被初始化,只有當其中的靜態內部參數被調用時,纔會被初始化。這樣保證懶加載。
通過靜態內部類的初始化staticInnerClassSingleton,jvm保證類的初始化只能有一個線程可以同時初始化同一個類,也就是獲取初始化InnerClass靜態類的鎖只會被一個線程獲取到,所以在InnerClass被初始化時,其中的靜態資源會優先被初始化。
相比雙重檢驗鎖:畢竟volatile禁止了指令重排,所以雙重檢驗鎖的效率肯定略低一點。
5.懶漢模式
public class HungrySingleton implements Serializable {
private static HungrySingleton ourInstance = new HungrySingleton();
public static HungrySingleton getInstance() {
return ourInstance;
}
private HungrySingleton() {
}
}
特點:在類加載的時候完成對單例類的初始化。
6.序列化對單例的破壞
流程
(1)ObjectInputStream中readObject讀取一個object
(2)ObjectInputStream中readObject0讀取
(3)readObject0方法中發現是一個object調用readOrdinaryObject
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
(4)readOrdinaryObject中有,表示只要是實現了序列化就new一個對象,否則返回null
obj = desc.isInstantiable() ? desc.newInstance() : null;
(5)調用了cons.newInstance()
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
(6)Reflection.getCallerClass()調用反射實現了new一個新的對象。
故前後不是同一個對象。
重寫:
(1)在readOrdinaryObject中反射完成一個對象的創建以後會調用desc.invokeReadResolve(obj)
(2)desc.invokeReadResolve(obj)中return readResolveMethod.invoke(obj, (Object[]) null);
(3) readResolveMethod.invoke調用原方法的readResolve方法,於是可以通過重寫readResolve實現返回原來的單例對象。
7. 反射攻擊單例
反射修改私有構造器的權限來new一個新的單例對象。
防止反射攻擊
懶漢式在多線程條件下無法防止反射
無法防止的原因:先利用構造器new了一個單例對象,然後getInstance()方法new了一個對象。
8. 枚舉完成單例
枚舉類型
反序列化無法攻擊枚舉
在ObjectInputStream中readEnum方法去讀取數據時,會從常量池中去尋找這些數據。所以枚舉中的data無論何時被獲取都是相等。
反射攻擊枚舉:報錯,獲取構造器失敗
查看源碼發現,枚舉沒有空構造器,必須要帶上參數
傳入參數後重試
查看源碼:發現構造器無法獲取枚舉對象
繼續使用jad EnumInstance.class生成EnumInstance.jad,裏面有如下代碼:
public final class EnumInstance extends Enum // final類
{
public static EnumInstance[] values()
{
return (EnumInstance[])$VALUES.clone();
}
public static EnumInstance valueOf(String name)
{
return (EnumInstance)Enum.valueOf(com/qianliu/creational/songleton/EnumInstance, name);
}
private EnumInstance(String s, int i)//私有構造器
{
super(s, i);
}
public static EnumInstance getInstance()
{
return INSTANCE;
}
public String getData()
{
return data;
}
public void setData(String data)
{
this.data = data;
}
public static final EnumInstance INSTANCE;
String data;
private static final EnumInstance $VALUES[];
static
{
INSTANCE = new EnumInstance("INSTANCE", 0);//在靜態模塊初始化單例對象
$VALUES = (new EnumInstance[] {
INSTANCE
});
}
}
Enum類似於餓漢模式聲明對象,類加載時完成初始化,再加上反射和io相關的類保證枚舉類型的單例。
使用枚舉類內部的方法:枚舉類內部的方法必須顯式聲明。
9. 容器實現單例
如果我們使用的過程中單例對象特別多,可以用這種方式實現單例
10. ThreadLocal實現特殊的單例
ThreadLocal可以保證每個線程內部的對象是單例的,不同線程直接按互不干擾
源碼:https://github.com/LUK-qianliu/design_pattern_in_all