Java单例模式Singleton及其七种实现方式

什么是单例模式

简述

单例模式(Singleton Pattern)是创建型设计模式中最简单且应用最为广泛的设计模式之一,单例模式属于创建型模式,提供了一种对象创建的思路。

使用单例模式时,目标类被要求确保有且仅有一个实例,对于系统任一对目标类的访问,不需要单独实例化类,只需要访问由目标类负责创建的唯一对象实例即可。

特点

  • 使用单例模式的目标类仅有一个实例
  • 由目标类自行创建管理唯一实例
  • 目标类对外提供实例访问方法

应用场景

出现以下场景,可以考虑使用单例模式

  • 某类实例需要被频繁的创建,使用后需要频繁销毁
  • 某类实例化耗时长、占用资源多且使用频繁
  • 某类实例频繁的与数据库或文件交互
  • 某类包含全局状态字段,或在业务逻辑上仅应有单一实例

优缺点

使用单例模式的优点:

  • 减少开销,因为内存中仅有一个实例,减少内存开销,又因为不需要频繁实例化类,减少了性能(时间)开销。
  • 避免对某资源的多重占用,例如对某些文件执行写操作时,单例可以保证对文件同时只有一个写操作。
  • 目标类对外提供唯一实例访问方法,实例可以作为共享资源的被访问。
  • 在某些场景下,某类在逻辑上仅应存在单一实例,使用单例模式可以帮助实现。

单例模式的不足:

  • 违背开闭原则(即对扩展开放,对修改封闭),想要对类进行扩展时,通常只能修改代码。
  • 易违背单一职责原则,即某一功能全部写在一个类中,容易变得冗杂。

单例模式的七种实现方式

懒汉式单例

顾名思义,此种方式实现的单例模式是在目标类要被使用时才创建实例,这种懒加载的好处是在目标类未被实际使用时,不会有实例占据内存空间。

线程不安全方式

此种实现方式线程不安全,在目标类实例还未被创建时,如果有两个线程同时访问了getInstance(),容易产生多个实例,破坏实例的唯一性,不推荐使用。

public class Singleton {
    public static Singleton instance;
    // 使用私有构造方法以避免被外部实例化 确保单例
    private Singleton(){}

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

线程安全方式

针对前一种线程不安全的实现方式,可以使用synchronized关键字修饰方法,确保方法同时只能被一个线程访问。虽然此种方式可以保证线程安全同时实现单例,但在单例被创建后,synchronized便失去了本场景下的作用(确保实例的唯一性),且会降低性能开销

public class Singleton {
    public static Singleton instance;
    // 使用私有构造方法以避免被外部实例化 确保单例
    private Singleton(){}
	//使用synchronized保证线程安全
    public synchronized static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

DCL双重检查锁定

前文提及懒汉式为保证线程安全引入synchronized导致性能开销提升,由此衍生出了双重检查锁定(Double Check Lock)的单例实现方式,使用volatile关键字保证可见性避免JVM指令重排引发的问题,同时使用synchronized保证实例创建过程唯一,既保证了懒加载的线程安全,又相较synchronized修饰方法减少了性能开销。

public class DCLSingleton {
    // 注意instance必须要由volatile关键字修饰 目的是保证instance变量的可见性 避免由于指令重排破坏单例的唯一性
    private static volatile DCLSingleton instance;
    
    private DCLSingleton() {}
    
    public static DCLSingleton getInstance() {
        if(instance == null) { //首先判断是否实例化
            //使用synchronized加锁保证实例创建唯一
            synchronized (DCLSingleton.class) {
                if(instance == null) {
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}

饿汉式单例

饿汉式单例的思路是利用static关键字,使目标类在类加载阶段就创建唯一实例,保证实例在被访问前就已经被创建,确保唯一性,线程安全。

静态变量

public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}

静态代码块

public class Singleton {

    private static Singleton instance;

    private Singleton(){}

    static{
        instance = new Singleton();    
    }
    
    public static Singleton getInstance(){
        return instance;
    }
}

静态内部类

在外部类被装载时,静态内部类不会跟着一起被装载,而是在静态内部类实际被使用的时候(getInstance被调用)才会装载并实例化,所以使用静态内部类实现单例模式具有懒加载的优点,同时JVM在装载类的过程中可以保证线程安全。

public class SingletonStaticClass {

    private SingletonStaticClass(){}
    
    public static final SingletonStaticClass getInstance(){
        return SingletonInstance.instance;
    }

    private static class SingletonInstance{
        private static final SingletonStaticClass instance = new SingletonStaticClass();
    }
}

枚举

枚举实现单例是最安全且写法最简单的,JVM会保证枚举类不能被反射且构造器只能被调用一次,彻底解决了其他单例模式容易被反射和序列化反序列化攻击的问题(见下文),推荐使用。

枚举类单例

public enum EnumSingleton {
    INSTANCE;
    public void method() {
        System.out.println("doSomething");
    }
}

已有类改造

public class EnumSingletonEnhance {
    private EnumSingletonEnhance(){}

    public enum SingletonEnum {
        SINGLETON_ENUM;
        private EnumSingletonEnhance instance = null;
        private SingletonEnum(){
            instance = new EnumSingletonEnhance();
        }
        public EnumSingletonEnhance getinstance(){
            return instance;
        }
    }
    
}
上一篇:matlab使用Copula仿真优化市场风险


下一篇:CCF 202012-2 期末预测之最佳阈值 C++代码(70分)