美文网首页Swift一些收藏产品
设计模式-创建者模式-单例模式

设计模式-创建者模式-单例模式

作者: 石头耳东 | 来源:发表于2022-05-10 23:56 被阅读0次

    零、 本文纲要

    • 一、 单例模式
    1. 饿汉式
      ① 静态变量方式
      ② 静态代码块
      ③ 枚举
    2. 懒汉式
      ① 静态方法(线程不安全)
      ② 静态synchronized方法(线程安全)
      ③ 静态方法双检锁(线程不安全)
      ④ 静态方法双检锁volatile优化(线程安全)
      ⑤ 静态内部类(线程安全)
    • 二、 单例模式问题解决
    1. 防止序列化破坏单例
    2. 防止反射破坏单例
    • 三、 JDK中存在的单例模式

    java.lang.Runtime类

    一、 单例模式

    1. 饿汉式

    • ① 静态变量方式

    Ⅰ 私有构造private Singleton() {}

    Ⅱ 静态变量private static Singleton instance = new Singleton();

    Ⅲ 静态方法获取单例public static Singleton getInstance() { return instance; }

    /**
     * 饿汉式
     * 静态变量创建类的对象
     */
    public class Singleton {
        //私有构造方法
        private Singleton() {}
    
        //在成员位置创建该类的对象
        private static Singleton instance = new Singleton();
    
        //对外提供静态方法获取该对象
        public static Singleton getInstance() {
            return instance;
        }
    }
    
    • ② 静态代码块

    Ⅰ 私有构造

    Ⅱ 静态变量

    Ⅲ 静态代码块赋值

    Ⅳ 静态方法获取对象

    /**
     * 饿汉式
     * 在静态代码块中创建该类对象
     */
    public class Singleton {
    
        //私有构造方法
        private Singleton() {}
    
        //在成员位置创建该类的对象
        private static Singleton instance;
    
        static {
            instance = new Singleton();
        }
    
        //对外提供静态方法获取该对象
        public static Singleton getInstance() {
            return instance;
        }
    }
    
    • ③ 枚举
    /**
     * 枚举方式
     */
    public enum Singleton {
        INSTANCE;
    }
    

    2. 懒汉式

    • ① 静态方法(线程不安全)
    /**
     * 懒汉式
     * 线程不安全
     */
    public class Singleton {
        //私有构造方法
        private Singleton() {}
    
        //在成员位置创建该类的对象
        private static Singleton instance;
    
        //对外提供静态方法获取该对象
        public static Singleton getInstance() {
    
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    存在问题:

    Ⅰ 多线程下单例难以保证

    sequenceDiagram
    participant t1 as Thread1
    participant t2 as Thread2
    t1->t1:instance == null
    t1->>+t2:Thread1时间片耗尽,Thread2使用CPU
    t2->t2:instance == null<br>instance = new Singleton()<br>return instance
    t2->>-t1:Thread2使用完CPU,Thread1分配使用
    t1->t1:instance = new Singleton()<br>return instance
    
    静态方法并发问题.png

    上图中不难看出,由于上下文切换的原因,本应单例的实例被创建了多次。

    Ⅱ 多线程下安全问题

     6: new           #3                  // class com/stone/Singleton
     9: dup
    10: invokespecial #4                  // Method "<init>":()V
    13: putstatic     #2                  // Field instance:Lcom/stone/Singleton;
    

    new指令:在java堆上为com/stone/Singleton对象分配内存空间,并将地址压入操作数栈顶;

    dup指令:复制操作数栈顶值,并将其压入栈顶,此时操作数栈上有连续相同的两个对象地址;

    invokespecial指令:需要从操作数栈顶弹出一个this引用,来调用实例初始化方法"<init>":()V,这一步会弹出一个之前入栈的对象地址;

    putstatic指令:将对象地址赋值给静态变量Singleton instance。

    sequenceDiagram
    participant t1 as Thread1
    participant t2 as Thread2
    t1->t1:new<br>dup<br>putstatic
    t1->>+t2:Thread1时间片耗尽,Thread2使用CPU
    t2->t2:instance == null不成立<br>直接返回instance
    t2->>-t1:Thread2使用完CPU,Thread1分配使用
    t1->t1:invokespecial
    
    指令重排异常.png

    由于发生了指令重排,instance未完成构造但被返回,故而出错。

    • ② 静态synchronized方法(线程安全)
    /**
     * 懒汉式
     *  线程安全
     */
    public class Singleton {
        //私有构造方法
        private Singleton() {}
    
        //在成员位置创建该类的对象
        private static Singleton instance;
    
        //对外提供静态方法获取该对象
        public static synchronized Singleton getInstance() {
    
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    由于synchronized是添加在方法上,多线程并发时会有争抢,性能有损耗。

    • ③ 静态方法双检锁(线程不安全)
    /**
     * 双重检查方式
     */
    public class Singleton { 
    
        //私有构造方法
        private Singleton() {}
    
        private static Singleton instance;
    
       //对外提供静态方法获取该对象
        public static Singleton getInstance() {
            //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
            if(instance == null) {
                synchronized (Singleton.class) {
                    //抢到锁之后再次判断是否为null
                    if(instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    synchronized关键字保证了原子性、可见性、有序性。

    但是这里的有序性是保证acquire barrier到release barrier内的指令不能和外部的指令重排,即我们monitor所管理的指令与外部指令不发生重排。

    10: monitorenter
    11: getstatic     #2                  // Field instance:Lcom/stone/Singleton;
    14: ifnonnull     27
    17: new           #3                  // class com/stone/Singleton
    20: dup
    21: invokespecial #4                  // Method "<init>":()V
    24: putstatic     #2                  // Field instance:Lcom/stone/Singleton;
    27: aload_0
    28: monitorexit
    29: goto          37
    32: astore_1
    33: aload_0
    34: monitorexit
    

    所以,其内部指令还是存在重排的可能。则会与静态方法(线程不安全)的案例发生一样的线程安全问题,出现空指针的异常。

    • ④ 静态方法双检锁volatile优化(线程安全)
    /**
     * 双重检查方式
     */
    public class Singleton {
    
        //私有构造方法
        private Singleton() {}
    
        private static volatile Singleton instance;
    
       //对外提供静态方法获取该对象
        public static Singleton getInstance() {
            //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
            if(instance == null) {
                synchronized (Singleton.class) {
                    //抢到锁之后再次判断是否为空
                    if(instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    当Thread1执行到volatile关键字修饰的instance变量的操作时,此处为instance = new Singleton();操作,则会在该操作上方添加一个写屏障,该操作下方添加一个读屏障。写屏障保证操作上方的写操作不能越过屏障重排,读屏障保证操作下方的读操作不能越过屏障重排。则Thread2要么读到null竞争锁,要么等instance = new Singleton();操作完成,读到生成的instance对象。

    • ⑤ 静态内部类(线程安全)
    /**
     * 静态内部类方式
     */
    public class Singleton {
    
        //私有构造方法
        private Singleton() {}
    
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        //对外提供静态方法获取该对象
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    二、 单例模式问题解决

    1. 防止序列化破坏单例

    重写readResolve方法,如下:

    public class Singleton implements Serializable {
    
        //私有构造方法
        private Singleton() {}
    
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        //对外提供静态方法获取该对象
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
        
        /**
         * 下面是为了解决序列化反序列化破解单例模式
         */
        private Object readResolve() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    2. 防止反射破坏单例

    在构造方法内做非空判断,如下:

    public class Singleton {
    
        //私有构造方法
        private Singleton() {
            /*
               反射破解单例模式需要添加的代码
            */
            if(instance != null) {
                throw new RuntimeException();
            }
        }
        
        private static volatile Singleton instance;
    
        //对外提供静态方法获取该对象
        public static Singleton getInstance() {
    
            if(instance != null) {
                return instance;
            }
    
            synchronized (Singleton.class) {
                if(instance != null) {
                    return instance;
                }
                instance = new Singleton();
                return instance;
            }
        }
    }
    

    三、 JDK中存在的单例模式

    • java.lang.Runtime

    Ⅰ 私有构造

    Ⅱ 静态变量

    Ⅲ 静态方法获取

    public class Runtime {
        private static Runtime currentRuntime = new Runtime();
    
        public static Runtime getRuntime() {
            return currentRuntime;
        }
        
        private Runtime() {}
        ... ...
    }
    

    四、 结尾

    以上即为设计模式-创建者模式-单例模式的全部内容,感谢阅读。

    相关文章

      网友评论

        本文标题:设计模式-创建者模式-单例模式

        本文链接:https://www.haomeiwen.com/subject/ycnrurtx.html