单例设计模式1

一、饿汉式

        单例设计模式,简单说:一个类只有一个实例对象。

        单例设计模式核心:因为内存、所以性能。

        饿汉式是指,这个类一旦加载,这个类的实例就被创建。而加载某个类到内存中由调用这个类的静态成员触发。单例模式里面的getInstance()就是静态方法。

        饿汉式代码步骤:1.构造函数私有化,2.内部创建本类静态实例,3.对外提供公共的访问实例的方法。

        饿汉式是线程安全的,因为类只会加载一次,加载的时候只会创建一份instance对象。

        引入一个相当有深度的单例文章:Java设计模式学习笔记,一:单例模式

public class Single {
    private static final Single instance = new Single();
    private Single(){}
    public static Single getInstance(){
        return instance;
    }
}

二、懒汉式

        懒汉式是指:只有调用这个单例类得时候,才去创建这个单例类的实例对象。比如下面的代码,但是下面的代码有线程安全问题。两个线程同时进入if判断,也就创建了两次这个对象,不满足需求。

public class Single {
    private static Single instance = null;
    private Single(){}
    public static Single getInstance(){
        if(instance == null){
            instance = new Single();
        }
        return instance;
    }
}

        解决办法如下代码:把getInstance()方法设为同步。这样做的好处是线程安全了,但是坏处就是每次调用getInstance()方法都要去判断同步锁,这样会浪费性能。实际上只是需要在第一次创建实例的时候,需要同步。

public synchronized static Single getInstance(){  
    if(instance == null){  
        instance = new Single();  
    }  
    return instance;  
} 

        改进以上的代码的方法就是“双重检验锁”。

    //A,B线程同时调用getInstance()方法
    public static Single getInstance(){
        if(instance == null){
            synchronized (Single.class){
                if(instance==null){
                    instance = new Single();
                }
            }

        }
        return instance;
    }
}

        注:在多线程情况下,JVM虚拟机可能会出现指令优化。在单例对象调用构造函数之前,就已经分配内存。这个时候,instance指向的其实是一个未初始化好的单例对象。所以,这个时候,我们需要在instance引用前面加上volatile关键字,禁止指令重排序,保证instance引用初始化的时候,单例对象已经被初始化。

        指令重排优化,可能会导致初始化单利对象和将该对象地址赋值给 instance 字段的顺序与上面 Java 代码中书写的顺序不同。

        例如:线程 A 在创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象设置为默认值。此时线程 A 就可以将分配的内存地址赋值给 instance 字段了,然而该对象可能还没有完成初始化操作。线程 B 来调用 newInstance() 方法,得到的 就是未初始化完全的单例对象,这就会导致系统出现异常行为。

        为了解决上述的问题,可以使用volatile关键字进行修饰 instance 字段。volatile 关键字在这里的含义就是禁止指令的重排序优化(另一个作用是提供内存可见性),从而保证 instance 字段被初始化时,单例对象已经被完全初始化。

         双重检验锁与延迟初始化的单例模式的问题根源

   public static Singleton getInstance() {
        if (instance == null) {
            synchorinzed(lock) {
                if (instance == null)
                    instance = new Singleton();//根源在这一句
            }
        }
        return instance;
    }


       下面重排序过程务必能够手写出来。

       instance = new Singleton(); 这一行代码可以拆分成三句。

       memory = allocate();  //1.分配对象的内存空间

       initInstance(memory); //2.初始化对象

       instance = memroy;    //3.设置instance引用指向这块内存

        上面的代码中,2和3可能会被重排序。

        另外一个并发线程B可能拿到的是没有被A线程初始化的对象。

        解决办法:(1)禁止指令重排序。所以使用 volatile 关键字修饰instance对象

                          (2)让这个可能会重排序的代码对其它线程不可见。

       使用嵌套类延时加载,如果多个线程同时调用getInstance()方法,那么会执行类初始化。类初始化的时候,会有一把锁,用于同步多个线程对同一个类的初始化。线程A在执行类初始化的时候,可能会出现了重排序,但是这个过程对其它线程是不可见的,所以可以保证正确的初始一个单例对象。

三、使用嵌套类的懒汉式

public class Single {
    private Single(){}
    private static class SingleFactory{
        private static final Single instance = new Single();
    }

    public static final Single getInstance(){
        return SingleFactory.instance;
    }
}

        这种方法是使用内部类。原理是:加载某各类由调用这个类的静态成员触发。这个内部类的静态成员就是本类的唯一的实例对象。

        当我们调用Single.getInstance()方法的时候,JVM加载类Single类。这个方法又调用嵌套类的静态成员intance,因此嵌套类被JVM加载,这个时候创建 new Single()对象。所以当我们没有调用getInstance()方法的时候,instance对象就没有被初始化。所以,当调用getInstance()方法的时候,才创建instance实例对象。这样,既保证了一个类只有一个实例对象,又是懒汉式的,还没有线程安全问题(原因:一个类只会被JVM加载一次)。

四、优缺点

        单例模式的优点:

          1.节约内存。一个类只有一个实例,内存开销比较小。

          2.提高系统性能。如果一个类对象的创建需要读取许多配置文件,可以在系统创建的时候,创建此对象,让它一直驻留在内存中。

        单例模式的缺点:

          1.扩展性很差。使用单例模式的类一般没有接口,没有面向接口编程,扩展比较困难,一般扩展需要修改代码。

五、使用场景

           1.需要频繁实例化的对象然后销毁的对象。

           2.创建对象时耗时过多或者是读取很多配置文件的对象。

六、记忆要点

        节约内存。提高性能。扩展性差。

七、使用枚举

        实力最简单

public enum Single {
    INTANCE;
}
public enum Single {
    /**
     * 默认实现
     */
    INTANCE;

    @lombok.Getter
    @lombok.Setter
    private String content;

   /**
     * 默认构造函数,省略public,可以在这里读取配置文件。
     */
    Single(){
        content = "default content";
    }

   //经测试,可以在包外调用此单例
}

分割线--------------------------------------------------------------------------------------------

刻意练习

        (1)单例设计模式高性能的写法

        (2)单例设计模式双重锁写法

        (3)饿汉式写法

        (4)单例设计模式应用场景是什么

        (5)优缺点

        (6)如何使用枚举来实现单例

下一篇:策略设计模式2

 

 

posted @ 2022-07-17 12:16  小大宇  阅读(26)  评论(0编辑  收藏  举报