Bootstrap

详解Java设计模式之单例模式(Singleton)

引言

  单例模式是一种创建型设计模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。在实际开发中,很多应用场景需要控制某个类在整个系统中的实例数量,保证其唯一性并提供全局访问点。这样的需求常常出现在配置管理、日志记录、数据库连接池、文件系统操作等模块中。通过单例模式,我们可以有效避免内存资源的浪费,提高系统性能,并确保类的实例始终一致。在本文中,我们将深入探讨单例模式的本质、应用场景、常见的实现方式、单例模式的优缺点、选用场景等,实现方式除了我们常见的饿汉式、懒汉式,本文还提到了另外三种方式,通过缓存和延迟加载的思想实现单例,通过枚举来实现单例,还有Lazy initialization holder class如有疏漏与不足,恳请不吝赐教,非常感谢!


  单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  单例模式的本质控制实例数目。单例模式是为了控制在运行期间,某些类的实例数目只能有一个。


一、场景问题

  我们在开发过程中,项目中都有与应用相关的配置文件,这些配置文件很多是由项目开发人员自定义的,在配置文件中定义一些应用需要的参数。在实际的项目中,这种配置文件多采用xml格式,也有采用properties格式的,使用Java来读取properties格式的配置文件比较简单。现在读取配置文件的内容,该如何实现呢


1.1、不用模式

  读取配置文件so easy这点小事情难不倒我,读取文件的内容,然后把文件内容放在相应的数据对象里面就可以了。我们以读取properties为例,代码示例如下:

/**
 * 读取配置文件
 */
public class AppConfig {

    private String paramA;

    private String paramB;

    // 注意这里只有访问参数的get方法,没有设置参数的set方法

    public String getParamA() {
        return paramA;
    }

    public String getParamB() {
        return paramB;
    }

    /**
     * 构造方法
     */
    public AppConfig() {

        // 读取配置文件
        readConfig();
    }

    private void readConfig(){

        Properties p = new Properties();
        InputStream in = null;
        try {
            // appConfig.properties文件放在了resources目录下了
            in = AppConfig.class.getResourceAsStream("/appConfig.properties");
            p.load(in);

            this.paramA = p.getProperty("paramA");
            this.paramB = p.getProperty("paramB");
        }catch (Exception e){
            System.out.println("读取文件报错e:" + e.getMessage());
            e.printStackTrace();
        }finally {
            try {
                in.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    @Override
    public String toString() {
        return "AppConfig{" +
                "paramA='" + paramA + '\'' +
                ", paramB='" + paramB + '\'' +
                '}';
    }
}

/**
 * 单例模式
 */
public class NoPatternMain {

    public static void main(String[] args) {
        AppConfig appConfig1 = new AppConfig();
        AppConfig appConfig2 = new AppConfig();

        System.out.println(appConfig1.toString());
        System.out.println(appConfig2.toString());
    }
}
/*打印结果:  
AppConfig{hashCode='21685669'paramA='aaa', paramB='bbb'}
AppConfig{hashCode='2133927002'paramA='aaa', paramB='bbb'}
注意一下hashCode是不一样的,这里是创建了两个对象。
*/

1.2、有何问题

  上面的实现很简单,很容易的就能实现要求的功能,真的这么简单吗? 仔细想想,有没有什么问题呢???

  客户端使用这个类的时候,是通过new一个AppConfig的实例来得到配置文件内容的。如果在系统运行中,有很多地方都需要使用配置文件的内容,也就是说很多地方都需要创建AppConfig对象的实例。换句话说,在系统运行期间,系统中会存在很多个AppConfig的实例对象,这有什么问题吗???

  当然有问题了,试想一下,每一个AppConfig实例对象里面都封装着配置文件的内容,系统中多个AppConfig实例对象,也就是说系统中会同时存在多份配置文件的内容,这样会严重浪费内存资源。如果配置文件内容较少,问题还小一点,如果配置文件内容本来就多的话,对于系统资源的浪费问题就大了。事实上,对于AppConfig这种类,在运行期间,只需要一个实例对象就是够了。

  把上面的描述进一步抽象一下,问题就出来了:在一个系统运行期间,某个类只需要一个类实例就可以了,那么应该怎样实现呢???


二、解决方案(重点)

  用来解决上述问题的一个合理的解决方案就是单例模式(Singleton)。仔细分析上面的问题,现在一个类能够被创建多个实例,问题的根源在于类的构造方法是公开的,也就是可以让类的外部来通过构造方法创建多个实例。换句话说,只要类的构造方法能让类的外部访问,就没有办法去控制外部来创建这个类的实例个数。

  要想控制一个类只被创建一个实例,那么首要的问题就是要收回创建实例的权限,让类自身来负责自己类实例的创建工作,然后由这个类来提供外部可以访问这个类实例的方法,这就是单例模式的实现方式。


2.1、结构说明

  • Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。
  • singletonData:一些自定义的属性
  • singletonOperation:一些自定义的方法

image-20250105161937515


2.2、使用模式

  在Java中,单例模式的实现又分为两种,一种是懒汉式,一种是饿汉式。饿汉式、懒汉式是一种比较形象的称谓。在创建对象实例的处理上,有不同的实现方式,但是它们的目的一致的。代码示例如下:


2.2.1、懒汉式示例

/**
 * 单例模式 懒汉式
 */
public class AppConfigLazy {

    private String paramA;

    private String paramB;

    // 注意这里只有访问参数的get方法,没有设置参数的set方法

    public String getParamA() {
        return paramA;
    }

    public String getParamB() {
        return paramB;
    }

    // 定义一个变量用来存储创建好的类实例,这里并不直接创建
    // 懒汉式的体现
    private static AppConfigLazy appConfig = null;

    /**
     * 定义一个方法来为客户端提供AppConfig类的实例
     * @return AppConfig
     */
    public static AppConfigLazy getInstance(){

        if (appConfig == null){
            appConfig = new AppConfigLazy();
        }
        return appConfig;
    }

    /**
     * 私有构造方法
     */
    private AppConfigLazy() {

        // 读取配置文件
        readConfig();
    }

    private void readConfig(){

        Properties p = new Properties();
        InputStream in = null;
        try {
            in = AppConfigLazy.class.getResourceAsStream("/appConfig.properties");
            p.load(in);

            this.paramA = p.getProperty("paramA");
            this.paramB = p.getProperty("paramB");
        }catch (Exception e){
            System.out.println("读取文件报错e:" + e.getMessage());
            e.printStackTrace();
        }finally {
            try {
                in.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    @Override
    public String toString() {
        return "AppConfigLazy{" +
                "hashCode='" + this.hashCode() + '\'' +
                "paramA='" + paramA + '\'' +
                ", paramB='" + paramB + '\'' +
                '}';
    }
}

  所谓懒汉式,既然是懒,那么在创建对象实例的时候就不着急,会一直等到第一次使用的时候才会创建,懒人嘛,总是推托不开的时候才去真正执行工作,因此在装载对象的时候不创建对象实例。


2.2.2、饿汉式示例

/**
 * 单例模式 饿汉式
 */
public class AppConfigHungry {

    private String paramA;

    private String paramB;

    // 注意这里只有访问参数的get方法,没有设置参数的set方法

    public String getParamA() {
        return paramA;
    }

    public String getParamB() {
        return paramB;
    }

    // 定义一个变量用来存储创建好的类实例,直接在这里创建类实例只能创建一次
    private static AppConfigHungry appConfig = new AppConfigHungry();

    /**
     * 定义一个方法来为客户端提供AppConfig类的实例
     * @return AppConfig
     */
    public static AppConfigHungry getInstance(){
        return appConfig;
    }

    /**
     * 私有构造方法
     */
    private AppConfigHungry() {

        // 读取配置文件
        readConfig();
    }

    private void readConfig(){

        Properties p = new Properties();
        InputStream in = null;
        try {
            in = AppConfigHungry.class.getResourceAsStream("/appConfig.properties");
            p.load(in);

            this.paramA = p.getProperty("paramA");
            this.paramB = p.getProperty("paramB");
        }catch (Exception e){
            System.out.println("读取文件报错e:" + e.getMessage());
            e.printStackTrace();
        }finally {
            try {
                in.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    @Override
    public String toString() {
        return "AppConfigHungry{" +
                "hashCode='" + this.hashCode() + '\'' +
                "paramA='" + paramA + '\'' +
                ", paramB='" + paramB + '\'' +
                '}';
    }
}
/**
 * 单例模式 懒汉式 饿汉式 
 */
public class UsePatternMain {

    public static void main(String[] args) {

        AppConfigLazy instanceLazy1 = AppConfigLazy.getInstance();
        AppConfigLazy instanceLazy2 = AppConfigLazy.getInstance();
        AppConfigLazy instanceLazy3 = AppConfigLazy.getInstance();

        AppConfigHungry instanceHungry1 = AppConfigHungry.getInstance();
        AppConfigHungry instanceHungry2 = AppConfigHungry.getInstance();
        AppConfigHungry instanceHungry3 = AppConfigHungry.getInstance();

        System.out.println(instanceLazy1);
        System.out.println(instanceLazy2);
        System.out.println(instanceLazy3);

        System.out.println(instanceHungry1);
        System.out.println(instanceHungry2);
        System.out.println(instanceHungry3);
    }
}

/*  打印结果
AppConfigLazy{hashCode='21685669'paramA='aaa', paramB='bbb'}
AppConfigLazy{hashCode='21685669'paramA='aaa', paramB='bbb'}
AppConfigLazy{hashCode='21685669'paramA='aaa', paramB='bbb'}
注意一下hashCode是一样的。

AppConfigHungry{hashCode='2133927002'paramA='aaa', paramB='bbb'}
AppConfigHungry{hashCode='2133927002'paramA='aaa', paramB='bbb'}
AppConfigHungry{hashCode='2133927002'paramA='aaa', paramB='bbb'}
注意一下hashCode是一样的。
懒汉式和饿汉式是单例模式的不同实现,目的是一样的
*/

  所谓饿汉式,既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是就在装载类的时候就创建对象实例。


三、模式介绍

3.1、认识单例模式

3.1.1、单例模式的功能

  单例模式是用来保证这个类在运行期间只会被创建一个类实例,另外,单例模式还提供了一个全局唯一访问这个类实例的访问点,就是getInstance方法。不管采用懒汉式还是饿汉式的实现方式,这个全局访问点是一样的。对于单例模式而言,不管采用何种实现方式,它都是只关心类实例的创建问题,并不关心具体的业务功能。


3.1.2、单例模式的范围(了解)

在多大范围内是单例呢?

  观察上面的实现可以知道,目前Java里面实现的单例是一个虚拟机的范围。因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的ClassLoader装载饿汉式实现单例类的时候就会创建一个类的实例。

  这就意味着如果一个虚拟机里面有很多个ClassLoader,而且这些ClassLoader都装载某个类的话,就算这个类是单例,它也会产生很多个实例。当然,如果一个机器上有多个虚拟机,那么每个虚拟机里面都应该至少有一个这个类的实例,也就是说整个机器上就有很多个实例,更不会是单例了。


3.2、饿汉式和懒汉式实现(重点)

  单例模式有两种典型的解决方案,一种叫懒汉式,另一种叫饿汉式。不管采用哪种方式,在运行期间都只会生成一个实例。而且这些类的全局访问点就是静态的getInstance方法。


3.2.1、懒汉式

  • 私有化构造方法

      要想在运行期间控制某一个类的实例只有一个,首要的任务就是要控制创建实例的地方,也就是不能随随便便就可以创建类实例,否则就无法控制所创建的实例个数了。怎样才能让类的外部不能创建一个类的实例呢?把构造方法私有化

  • 提供获取实例的方法,并把获取实例的方法变成静态的,同时实现控制实例的创建

      构造方法被私有化了,外部创建不了类实例就没有办法调用这个对象的方法,就实现不了功能调用。但是我们可以定义一个类方法getlnstance,通过这个类方法来创建实例,方便外面使用。在getlnstance方法里面判断一下存放实例的属性有没有值,有则返回,没有创建后返回。

  • 定义存储实例的属性,并把这个属性也定义成静态的

      用一个属性来记录自己创建好的类实例。当第一次创建后,就把这个实例保存下来,以后就可以复用这个实例,而不是重复创建对象实例了。这个属性变量是在第一次创建时使用的,由于要在一个静态方法里面使用,所以这个属性被迫成为一个类变量,要强制加上static,这里并没有使用static的特性。

  • 示例代码如下:

    public class Singleton {
        
        // 3、定义一个类变量,用来存储创建好的实例
        // 缓存的思想,把频繁使用的数据存到内存中去
        private static Singleton singleton = null;
        
        // 1、私有化构造方法,只能类内部使用,方便控制创建示例的数目
        private Singleton() {
        }
        
        // 2、定义一个为外都提供实例的类方法
        // 延迟加载思想的体现
        public Singleton getInstance(){
            // 判断存储实例的变量有没有值
            if (singleton == null){
                //  如果没有值创建实例赋给变量后在返回
                singleton = new Singleton();
            }
            // 如果有值直接返回
            return singleton;
        }
    }
    

3.2.2、饿汉式

  • 私有化构造方法

  • 提供获取实例的方法,并把获取实例的方法变成静态的,方法内直接返回实例

    实例在定义属性的时候已经创建好了,这里直接返回就行。

  • 定义存储实例的属性,并把这个属性也定义成静态的,同时创建类的实例

    static变量在类装载的时候进行初始化,多个实例的static变量会共享同一块内存区域。这里用到了static的特性。

  • 代码示例:

    public class SingletonHungry {
    
        // 3、定义一个类变量,用来存储创建好的实例
        // 直接在这里创建实例,只能创建一次
        private static SingletonHungry singleton = null;
    
        // 1、私有化构造方法,只能类内部使用,方便控制创建示例的数目
        private SingletonHungry() {
    
        }
    
        // 2、定义一个为外都提供实例的类方法
        public SingletonHungry getInstance(){
            // 直接返回已经创建好的实例
            return singleton;
        }
    }
    

3.3、延迟加载的思想

  什么是延迟加载呢?通俗的来说,延迟加载就是一开始不要加载资源或者数据,一直等,等到马上就要使用这个资源或者数据了,躲不过去了才加载,所以也称Lazy Load,不是懒惰哈,是延迟加载,这在实际开发中是一种很常见的思想,尽可能地节约资源。单例模式的懒汉式实现方式体现了延迟加载的思想。

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

3.4、缓存的思想

  什么是缓存思想?当某些资源或者数据被频繁地使用,而这些资源或数据存储在系统外部,比如数据库、硬盘文件等,那么每次操作这些数据的时候都得从数据库或者硬盘上去获取,速度会很慢,将造成性能问题。一个简单的解决方法就是:把这些数据缓存到内存里面,每次操作的时候,先到内存里面找,看有没有这些数据,如果有,就直接使用,如果没有就获取它,并设置到缓存中,下一次访问的时候就可以直接从内存中获取了,从而节省大量的时间。当然,缓存是一种典型的空间换时间的方案。单例模式的懒汉式实现还体现了缓存的思想,缓存也是实际开发中常见的功能。


3.5、Java中缓存的基本实现

  在Java开发中最常见的一种实现缓存的方式就是使用Map,基本步骤如下。

  • 先到缓存里面查找,看看是否存在需要使用的数据。
  • 如果没有找到,那么就创建一个满足要求的数据,然后把这个数据设置到缓存中,以备下次使用。如果找到了相应的数据,或者是创建了相应的数据,那就直接使用这个数据。
public class JavaCache {

    /**
     * 缓存数据容器,定义成map方便访问,直接根据key来获取value
     */
    private Map<String,Object> map = new HashMap<String, Object>();

    /**
     * 从缓存中获取值
     *
     * @param key  map 中的key
     * @return key 对应的value
     */
    public Object getValue(String key){

        // 先从缓存中去取值
        Object value = map.get(key);
        // 判断缓存中有没有值
        if (value == null){

            // 如果没有值,去数据库或者读取文件等获取数据,这里只是演示,直接写个假的
            value = "这是获取到的的value";
            // 把获取到的value存到map中去
            map.put(key,value);
        }
        // 此时把value返回
        return value;
    }
}

  这里只是缓存的基本实现,还有很多功能都没有考虑,比如缓存的清除,缓存的同步等。当然,Java的缓存还有很多实现方式,也是非常复杂的,也有很多专业的缓存框架。


3.6、利用缓存实现单例模式

  应用Java缓存的知识,可以变相实现Singleton模式,也算是一个模拟实现吧。每次都先从缓存中取值。只要创建一次对象实例后,就设置了缓存的值,那么下次就不用再创建了。虽然不是很标准的做法,但是同样可以实现单例模式的功能。为了简单,先不去考虑多线程的问题,代码示例如下:

public class SingletonByMap {

    private final static String DEFAULT_KEY = "cctop10";

    private static Map<String,SingletonByMap> map = new HashMap<String, SingletonByMap>();

    private SingletonByMap(){

    }

    public static SingletonByMap getInstance(){

        SingletonByMap instance = map.get(DEFAULT_KEY);
        if (instance == null){
            instance = new SingletonByMap();
            map.put(DEFAULT_KEY,instance);
        }
        return instance;
    }
}

  实现模式的方式有很多种,并不是只有懒汉式、饿汉式,上面这种也能实现单例所要求的功能,只不过实现比较麻烦,不是太好而已。,模式是经验的积累,模式的参考实现并不一定是最优的,对于单例模式,后面还会提到更好的实现方式。


3.7、单例模式的优缺点(重点)

3.7.1、时间和空间

懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。

饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。


3.7.2、线程安全

饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。

懒汉式是线程不安全的,在多线程环境中可能会创建多个实例来。那怎样解决懒汉式线程安全的问题呢?双重检测加锁

public synchronized Singleton getInstance(){  
        if (singleton == null){
        
            singleton = new Singleton();
        }
        return singleton;
    }
}

  上面这个示例应该也可以解决问题的,是的可以解决,但是这样还不是很好,因为这样会降低访问速度,每次都要判断。sychronized关键字虽然能保证线程安全,但它的开销包括获取锁、线程上下文切换、锁竞争等,这些都会导致性能下降。要提高性能,可以通过减少同步的粒度、使用更细粒度的锁。那怎样才能更好呢?

  使用双重检测加锁的方式来实现,就可以既实现线程安全,又能够使性能不受到很大的影响。所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。来看一下代码示例

public class SingletonLazyPlus {

    private volatile static SingletonLazyPlus instance = null;

    private SingletonLazyPlus() {

    }

    public SingletonLazyPlus getInstance(){
        if (instance == null){
            synchronized (SingletonLazyPlus.class){
                if (instance == null){
                    instance = new SingletonLazyPlus();
                }
            }
        }
        return instance;
    }
}

  双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

  这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是在第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。


3.8、Lazy initialization holder class

  根据上面的分析,常见的两种单例实现方式都存在小小的缺陷,那么有没有一种方案,既能够实现延迟加载,又能够实现线程安全呢?Lazy initialization holder class方式,使用了类级内部类多线程缺省同步锁的知识,很巧妙地同时实现了延迟加载和线程安全。


什么是类级内部类?

  • static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。
  • 类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
  • 类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
  • 类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。

多线程缺省同步锁这是什么,从来没听说过?

  在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为帮我们执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:

  • 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
  • 访问final字段时
  • 在创建线程之前创建对象时
  • 线程可以看见它将要处理的对象时

解决思路

  要想很简单地实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式。这样会浪费一定的空间吗?因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。

  如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。来看一下代码示例:

public class Singleton {

    /**
     * 私有化构造方法
     */
    private Singleton() {

    }

    /**
     * 类级内部类,也就是静态内部类,该内部类的实例和外部类的实例没有绑定关系,
     * 而且只有被调用到的时候才会装载,从而实现了延迟加载。
     */
    private static class SingletonInnerClass{

        // 静态初始化器由JVM来保证线程安全。
        private static Singleton instance = new Singleton();
    }

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

  当getInstance方法第一次被调用的时候,它第一次读取SingletonInnerClass.instance,导致SingletonInnerClass类得到初始化。而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。


3.9、单例和枚举

  简单回顾一下什么枚举?在 Java 中,枚举(enum)是用来定义一组常量的特殊类型。enum 本质上是一个类,但它限制了该类只能有固定的几个实例。与传统的常量(如 static final 字段)相比,枚举具有更强的类型安全性、可维护性和可读性。还有一种表达是:一个类的对象,认为个数是有限且固定的,可以一一列举出来

  总结一下枚举的特点:

  • Java的枚举类型本质上是功能齐全的类,可以拥有自己的属性和方法
  • Java枚举通过公有的静态 final 域来为每个枚举常量创建实例
  • 枚举类型是单例的泛型类,每个枚举常量实际上是该类的一个唯一实例。

  接下来看一下枚举是怎样实现单例的,代码示例如下:

public enum SingletonEnum {

    /**
     * 定义一个枚举元素,它就代表了Singleton唯一的实例
     */
    uniqueInstance("三国帅帅的超",18);


    // 可以定义自己的属性和方法
    private String name;

    private Integer age;

    SingletonEnum(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "SingletonEnum{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

  使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化的机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。


3.10、何时选用

  当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题。


总结

  单例模式是为了保证某个类在整个系统中只有一个实例,并通过一个全局的访问点让所有部分都能共享这个实例。它的主要作用是避免重复创建对象,节省资源,确保数据的一致性。

  这里我们通过几个问题总结一下全文:

  • 单例模式的本质是什么?
  • 饿汉式和懒汉式是怎样实现的,怎样保证线程安全?
  • 除了饿汉式、懒汉式还有其他实现单例的方式吗?
  • 缓存的思想、延迟加载的思想还记得吗?
  • 单例模式优点是什么?
;