單例模式多種玩法

餓漢單例模式

簡單餓漢單例模式

public class HungrySingleton {
    private static final HungrySingleton hungrySington = new HungrySingleton();

    private HungrySingleton() {
    }
    public static HungrySingleton getInstance(){
        return hungrySington;
    }
}

靜態代碼塊實現餓漢模式

public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungrySington;

    static {
        hungrySington = new HungryStaticSingleton();
    }

    private HungryStaticSingleton() {
    }

    public static HungryStaticSingleton getInstance() {
        return hungrySington;
    }
}

懶漢單例模式

簡單懶漢單例模式(線程不安全)

public class LazySimplySington {
    private static LazySimplySington lazy;

    private LazySimplySington() {
    }

    //jdk1.8之後對synchronized性能優化不少
    //不可避免還是存在一定的性能問題
    public synchronized static LazySimplySington getInstance() {
        if (lazy == null) {
            lazy = new LazySimplySington();
        }
        return lazy;
    }
}

雙重檢查實現單例模式(線程安全)

注意指令重排序問題,需要單獨加voliate關鍵字

public class LazyDoubleCheckSington {

    private volatile static  LazyDoubleCheckSington lazy = null;

    private LazyDoubleCheckSington() {
    }

    //jdk1.8之後對synchronized性能優化不少
    //不可避免還是存在一定的性能問題
    public static LazyDoubleCheckSington getInstance() {
        if (lazy == null) {
            synchronized (LazyDoubleCheckSington.class) {
                if (lazy == null) {
                    lazy = new LazyDoubleCheckSington();
                    //指令重排序的問題:也就是 第二步和第三步會顛倒,
                    // 解決方式 變量上加voliate,讓線程可見

                    //CPU執行時候會轉換成JVM指令執行
                    //1.分配內存給這個對象
                    //2.初始化對象
                    //3.將初始化好的對象和內存地址建立關聯,賦值
                    //4.用戶初次始化
                }
            }
        }
        return lazy;
    }
}

內部類實現單例模式(線程安全)

public class LazyInnerClassSington {

    //雖然構造方法有了,但是逃不過反射的法眼
    private LazyInnerClassSington() {      
    }
    //懶漢式單例
    //LazyHolder裏面的邏輯需要等到外部方法調用時才執行
    //巧妙利用了內部類的特性
    //JVM底層執行邏輯,完美的避免了線程安全問題
    public static final LazyInnerClassSington getInstance(){
        return LazyHolder.lazy;
    }
    private static class LazyHolder{
        private static final LazyInnerClassSington lazy = new LazyInnerClassSington();
    }
}

暴力破解單例

反射暴力破解

public class LazyInnerClassSingtonTest {
    public static void main(String[] args) {
        try {
            //反射,破壞了單例
            Class<?> clazz = LazyInnerClassSington.class;
            Constructor<?> c = clazz.getDeclaredConstructor(null);
            c.setAccessible(true);
            Object o = c.newInstance();
            Object o2 = c.newInstance();
            System.out.println(o == o2);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述

解決方式:

在構造函數內部添加判斷

//雖然構造方法有了,但是逃不過反射的法眼
    private LazyInnerClassSington() {
        if (LazyHolder.lazy != null){
            throw new RuntimeException("不允許構建多個實例");
        }
    }

測試:
在這裏插入圖片描述
完整代碼:

public class LazyInnerClassSington {

    //雖然構造方法有了,但是逃不過反射的法眼
    private LazyInnerClassSington() {
        if (LazyHolder.lazy != null){
            throw new RuntimeException("不允許構建多個實例");
        }
    }
    //懶漢式單例
    //LazyHolder裏面的邏輯需要等到外部方法調用時才執行
    //巧妙利用了內部類的特性
    //JVM底層執行邏輯,完美的避免了線程安全問題
    public static final LazyInnerClassSington getInstance(){
        return LazyHolder.lazy;
    }
    private static class LazyHolder{
        private static final LazyInnerClassSington lazy = new LazyInnerClassSington();
    }
}

序列化和反序列化暴力破解

來個餓漢單例

//反序列化時導致單例破壞
public class SeriableSingleton implements Serializable {

    //序列化就是說把內存中的狀態通過轉換成字節碼的形式
    //從而轉換一個IO流,寫入到其他地方(可以是磁盤、網絡IO)
    //內存中狀態給永久保存下來了

    //反序列化
    //講已經持久化的字節碼內容,轉換爲IO流
    //通過IO流的讀取,進而將讀取的內容轉換爲Java對象
    //在轉換過程中會重新創建對象new

    public  final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(){}

    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }

破解

public class SeriableSingletonTest {
    public static void main(String[] args) {

        SeriableSingleton s1 = null;
        SeriableSingleton s2 = SeriableSingleton.getInstance();

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


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

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

解決方式resolve方法

加個方法:

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

完整代碼:

//反序列化時導致單例破壞
public class SeriableSingleton implements Serializable {

    //序列化就是說把內存中的狀態通過轉換成字節碼的形式
    //從而轉換一個IO流,寫入到其他地方(可以是磁盤、網絡IO)
    //內存中狀態給永久保存下來了
    //反序列化
    //將已經持久化的字節碼內容,轉換爲IO流
    //通過IO流的讀取,進而將讀取的內容轉換爲Java對象
    //在轉換過程中會重新創建對象new
    public  final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(){}

    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }


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

註冊類單例

枚舉實現單例(推薦)

可以同時實現防止反射和序列化反序列化暴力破解

public enum EnumSingleton {
    INSTANCE;

    private String name;

    public String getData() {
        return name;
    }

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

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

使用jad工具查看枚舉類的反編譯代碼

在這裏插入圖片描述
在這裏插入圖片描述
反編譯代碼:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingleton.java

package com.example.sington.register;


public final class EnumSingleton extends Enum
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(com/example/sington/register/EnumSingleton, name);
    }

    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public Object getData()
    {
        return data;
    }

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

    public static EnumSingleton getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingleton INSTANCE;
    private Object data;
    private static final EnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }
}

容器實現單例

public class ContainerSingleton {

    private ContainerSingleton() {
    }

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

    public static Object getBean(String className) {
        synchronized (ioc){
            if (!ioc.containsKey(className)) {
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return ioc.get(className);
        }
    }
}
public class Pojo {
}

public class ContainerSingletonTest {
    public static void main(String[] args) {


        try {
            long start = System.currentTimeMillis();
            ConcurrentExecutor.execute(new ConcurrentExecutor.RunHandler() {
                public void handler() {
                    Object obj = ContainerSingleton.getBean("com.example.Pojo");
                    System.out.println(System.currentTimeMillis() + ": " + obj);
                }
            }, 10,6);
            long end = System.currentTimeMillis();
            System.out.println("總耗時:" + (end - start) + " ms.");
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

在這裏插入圖片描述

線程間實現單例ThreadLocal

public class ThreadLocalSingleton {


    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            new ThreadLocal<ThreadLocalSingleton>() {
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton() {
    }

    public static ThreadLocalSingleton getInstance() {
        return threadLocalInstance.get();
    }
}
public class ExectorThread implements Runnable {
    @Override
    public void run() {
//        LazySimplySington instance = LazySimplySington.getInstance();
//        LazyDoubleCheckSington instance = LazyDoubleCheckSington.getInstance();
        ThreadLocalSingleton instance = ThreadLocalSingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+" ---"+instance);
        ThreadLocalSingleton instance2 = ThreadLocalSingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+" ---"+instance2);
        ThreadLocalSingleton instance3 = ThreadLocalSingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+" ---"+instance3);
        ThreadLocalSingleton instance4 = ThreadLocalSingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+" ---"+instance4);
        ThreadLocalSingleton instance5 = ThreadLocalSingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+" ---"+instance5);

        System.out.println(Thread.currentThread().getName()+" ---"+instance);
    }
}

public class ThreadLocalSingletonTest {
    public static void main(String[] args) {

        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());

        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("End");

    }
}

在這裏插入圖片描述
可以看到線程間是單例的。

單例模式的本質

本質: 控制實例數目。(研磨設計模式)

筆記和代碼地址

代碼:
https://github.com/hufanglei/pattern-learn/tree/master/src/main/java/com/example/sington

筆記https://blog.csdn.net/baidu_21349635/article/details/106067581


微信訂閱號:搜索: 怒放de每一天
在這裏插入圖片描述


個人微信公衆號:
搜索: 怒放de每一天
不定時推送相關文章,期待和大家一起成長!!
在這裏插入圖片描述


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章