设计模式-单例模式(创建型)

时间:2024-06-11 16:37:27

创建型-单例模式

了解单例

单例模式是一种创建型设计模式,它提供了一种创建对象的最佳方式;它必须保证:

  1. 单例类只能有一个实例化对象;
  2. 单例类必须创建自己的唯一实例;
  3. 单例类必须给其他对象提供实例;

另外:

  1. 它的目的是:确保一个类只用一个实例,并提供一种全局访问入口来访问该实例
  2. 设计思想:在获取实例的时候判断实例是否存在,如果存在,则直接返回,如果不存在则创建实例;
  3. 关键代码:构造方法私有化;

角色

  1. 单例类:包含单例实例的类
  2. 静态成员变量:用于存储单例的静态成员变量,final修饰防止被继承
  3. 获取实例方法:静态方法,用于获取单例实例
  4. 私有构造方法:防止外部直接实例化类
  5. 线程安全处理:确保多线程环境下单例创建的安全性

实现方式

饿汉式单例

特点:类一加载就实例化单例对象

public class Mgr01 {
    //静态成员变量存储单例,final修饰防止被继承
    private final static Mgr01 INSTANCE = new Mgr01();

    //构造方法私有化,防止外部直接实例化
    private Mgr01() {
    }
    //静态方法,用于获取单例
    public static Mgr01 getInstance(){
        return INSTANCE;
    }
}

另一种写法,在静态代码块中实例化对象

public class Mgr02 {
    private final static Mgr02 INSTANCE ;
    static {
        INSTANCE = new Mgr02();
    }
    public static Mgr02 getMgr02() {
        return INSTANCE;
    }
}

懒汉式单例

特点: 使用单例时才实例化对象

线程不安全写法:

    public class Mgr03 {
        private static Mgr03 INSTANCE;
        private Mgr03() {
        }

        public static Mgr03 getInstance(){
            //模拟执行其他操作所用的时间
            if( INSTANCE == null){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                INSTANCE = new Mgr03();
            }
            return INSTANCE;
        }
}

synchronized锁获取实例静态方法,保证线程安全:

public class Mgr04 {
    private static Mgr04 INSTANCE;
    private Mgr04() {
    }
    public static synchronized Mgr04 getInstance(){
        //模拟执行其他操作所用的时间
        if( INSTANCE == null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            INSTANCE = new Mgr04();
        }
        return INSTANCE;
    }
}

双重检验锁(DCL)单例:

  1. 第一层null值检测是为了在已经存在单例的情况下不需要等锁提高效率,第二次null判断是为了保证单例。
  2. volatile关键字的作用:如果不使用volatile关键字那么,创建单例过程可能被拆分为以下几步,①为单例对象分配内存空间;②初始化单例对象;③将INSTANCE变量指向分配的内存空间。在没有volatile 关键字的情况下,步骤②和③可能会被重排序。这就可能导致其他线程在执行getInstance() 方法时,看到的 INSTANCE 变量已经被赋值,但单例对象并没有被完成初始化。
public class Mgr06 {
    private static volatile Mgr06 INSTANCE;//volatile 是为了防止JVM中语句重排
    private Mgr06() {
    }
    public static  Mgr06 getInstance(){
        //这个判断可以屏蔽很多操作,很多线程到这,如果已INSTANCE已经存在,可以减少下面代码的执行,提升效率
        if( INSTANCE == null){
            synchronized (Mgr06.class){
                if(INSTANCE == null){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    INSTANCE = new Mgr06();
                }
            }

        }
        return INSTANCE;
    }
}

静态内部类单例

这种方法是通过JVM保证单例,JVM在加载外部类时,只加载一次,且内部类在使用时才会加载,也就是第一次调用获取实例的方法时候才会调用。
其中,内部类私有化,内部类中的静态变量也私有化;

public class Mgr07 {
    private static class MGR_07{
        private final static Mgr07 INSTANCE = new Mgr07();
    }
    public static Mgr07 getInstance(){
        return MGR_07.INSTANCE;
    }
}

枚举单例

枚举单例不但可以保证单例,还可以防止反序列化,因为枚举没有构造方法。

public enum Mgr08 {
    INSTANCE
}