Java設計模式 ------ 單例模式

單例模式

餓漢式

普通餓漢式

public class HungrySingleton {

    // 不加final 可能會通過反射機制覆蓋
    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();

    // 1. 構造方法私有
    private HungrySingleton() {
    }

    // 2. 全局訪問
    public static HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
}

靜態代碼塊

public class HungryStaticSingleton {

    private static final HungryStaticSingleton HUNGRY_SINGLETON;

    static {
        HUNGRY_SINGLETON = new HungryStaticSingleton();
    }

    // 1. 構造方法私有
    private HungryStaticSingleton() {
    }

    // 2. 全局訪問
    public static HungryStaticSingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
}

序列化單例

public class SerializableSingleton implements Serializable {

    public final static SerializableSingleton INSTANCE = new SerializableSingleton();

    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }

    // 序列化破壞單例的解決方式
    // 重寫readResolve 方法,只不過是覆蓋了反序列化出來的對象
    // 還是創建了兩次,發生在JVM層面,相對來說比較安全
    // 之前反序列化出來的對象會被GC回收
    private Object readResolve() {
        return INSTANCE;
    }
}

readResolve()

public class SerializableSingletonTest {

    public static void main(String[] args) {
        SerializableSingleton s1 = null;
        SerializableSingleton s2 = SerializableSingleton.getInstance();

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("SerializableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SerializableSingleton) ois.readObject();
            ois.close();

            System.out.println(s1 == s2);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

懶漢式

存在線程問題的懶漢式

public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    // 構造方法私有化
    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

方法加synchronized的懶漢式(類鎖)

public class LazySimpleSingleton {

    private static LazySimpleSingleton lazySimpleSingleton = null;

    // 構造方法私有化
    private LazySimpleSingleton() {
    }

    // synchronized 性能問題
    public synchronized static LazySimpleSingleton getInstance() {
        if (lazySimpleSingleton == null) {
            lazySimpleSingleton = new LazySimpleSingleton();
        }
        return lazySimpleSingleton;
    }
}

synchronized 加載 static修飾的方法時,會變成類鎖。
類鎖 是鎖住整個類,當有多個線程來訪問getInstance()時候將會被阻塞,直到擁有這個類鎖的對象被銷燬或者主動釋放了類鎖。

synchronized修飾普通方法,即爲 對象鎖
多個線程訪問同一個對象實例的這個方法時,是會同步的,並且只有一個線程執行完,另一個線程纔會執行

雙重鎖檢查

改良後的
對象鎖 --> 類鎖

public class LazyDoubleCheckSingleton {

    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    // 構造方法私有化
    private LazyDoubleCheckSingleton() {
    }

    // 雙重檢查鎖
    public synchronized static LazyDoubleCheckSingleton getInstance() {
        if (lazyDoubleCheckSingleton == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazyDoubleCheckSingleton == null) {
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                    // CPU執行時候會轉換成JVM指令
                    // 2 3 會有指令重排序
                    // 1. 分配內存給這個對象
                    // 2. 初始化對象
                    // 3. 將初始化好的對象和內存地址建立關聯, 賦值
                    // 4. 用戶初次訪問
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton()這句,這並非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。

  1. lazyDoubleCheckSingleton 分配內存
  2. 調用 LazyDoubleCheckSingleton 的構造函數來初始化成員變量
  3. lazyDoubleCheckSingleton對象指向分配的內存空間(執行完這步 lazyDoubleCheckSingleton 就爲非 null 了)

volatile起到禁止指令重排的作用,在它賦值完成之前,就不會調用讀操作(lazyDoubleCheckSingleton== null)

靜態內部類

靜態內部類不會在單例加載時加載,當調用 getInstance() 方法時纔會進行加載,達到類似懶漢式效果,並且也是線程安全的。

// 全程沒有用到synchronized關鍵字
// 內部類比外部類優先加載
// 性能最優的一種寫法
public class LazyInnerClassSingleton {

    // 雖然構造方法私有,但是逃不過構造方法
    private LazyInnerClassSingleton() {
        if (LazyHolder.LAZY != null) {
            throw new RuntimeException("不允許構建多個實例");
        }
    }

    // 懶漢式單例
    // LazyHolder 裏面的邏輯需要等到外部方法調用時才執行
    // 巧妙利用了內部類的特性
    // JVM底層執行邏輯,完美地避免了線程安全問題
    public static final LazyInnerClassSingleton getInstance() {
        return LazyHolder.LAZY;
    }

    private static class LazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

破壞單例

public class LazyInnerClassSingletonTest {

    public static void main(String[] args) {

        try {
            // 破壞了單例
            Class<?> clazz = LazyInnerClassSingleton.class;
            Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null);
            // 授權 強制訪問
            declaredConstructor.setAccessible(true);
            Object o1 = declaredConstructor.newInstance();

            Object o2 = LazyInnerClassSingleton.getInstance();

            System.out.println(o1 == o2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

枚舉

// 懶漢式線程安全
public enum EnumSingleton {
    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance() {
        return INSTANCE;
    }
}

能防止反序列化時重新創建新的對象
EnumSingleton的定義利用的enum是一種特殊的class。代碼中的第一行INSTANCE會被編譯器編譯爲EnumSingleton本身的一個對象.當第一次訪問EnumSingleton.INSTANCE時會創建該對象,並且enum變量的創建是線程安全的.

ThreadLocal

ThreadLocal<>爲每一個線程提供一個獨立的變量副本

public class ThreadLocalSingleton {

    private ThreadLocalSingleton() {
    }

    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            ThreadLocal.withInitial(() -> new ThreadLocalSingleton());

    private static ThreadLocalSingleton getInstance() {
        return threadLocalInstance.get();
    }
}

容器化單例

public class ContainerSingleton {
    private ContainerSingleton() {
    }

    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getBean(String className) {
        if (!ioc.containsKey(className)) {
            Object obj = null;
            try {
                obj = Class.forName(className).newInstance();
                ioc.put(className, obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return obj;
        }
        return ioc.get(className);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章