【面试八股总结】单例模式实现详解

一、基本概念

        单例设计模式是⼀种确保⼀个类只有⼀个实例,并提供⼀个全局访问点来访问该实例的创建模式。

关键概念:

  • 一个私有构造函数:确保只能单例类自己创建实例
  • 一个私有静态变量:确保只有一个实例,私有静态变量用于保存该类的唯一实例
  • 一个公有静态函数:给使用者提供调用方法

优点:

        有些实例全局只需要⼀个,使用单例模式就可以避免一个全局使用的类,频繁的创建与销毁,耗费系统资源。

二、实现方法

单例模式有两种类型:

  • 懒汉式:在真正需要使用对象时,采取创建该单例类对象
  • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用

1. 懒汉式(线程不安全)

        懒汉式创建对象方法在程序使用前会先判断该对象是否已经实例化(判断是否为空),若已实例化直接返回该类对象,否则执行实例化。

class Singleton{
    private static Singleton instance;
    private Singleton(){}    // 构造函数为私有,确保外界不可以使用new创建该类实例
    
    public static Singleton GetInstance(){    // 该方法为本类实例的唯一全局访问点
        if (instance == NULL){         // 若实例不存在,则new一个实例,否则返回已有实例
            instance = new Singleton();
        }
        return instance;
    }
};

2. 懒汉式(线程安全) 

        在这里考虑线程安全问题,如果多个线程同时判断instance为空,那么他们都会去实例化一个Singleton对象,就违背了单例模式的原则。为了保证线程安全,考虑加上锁。

public static synchronized Singleton getInstance() {
    synchronized(Singleton.class) {    
        if (instance== NULL) {         
            instance= new Singleton();
        }
    }
    return instance;
}

3. 双重检查锁

        上述代码虽然解决了线程安全问题,但是性能降低了。因为,即使实例已经实例化了, 但是锁还在,每次还是只能拿到锁的线程进入该方法使线程阻塞,等待时间过长。

        将锁的位置改变,并且多加了⼀个检查。也就是,先判断实例是否已经存在,若已经存在了,则不会执行判断方法内的有锁方法了。 而还没有实例化的时候多个线程进去也没有事,因为里面的方法有锁,只会让⼀个线程进⼊最内层方法并实例化实例。如此⼀来,最多也就是第⼀次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题。

class Singleton{
    private volatile static Singleton uniqueInstance;
    private Singleton(){}  
    
    public static Singleton getInstance() {
        if (uniqueInstance== NULL) {    
            synchronized(Singleton.class) {    // 加锁,只有一个线程获得该锁并进行初始化
                if (uniqueInstance== NULL) {         
                    uniqueInstance= new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

为什么使用volatile关键字修饰uniqueInstance变量?

uniqueInstance= new Singleton();

上述一行代码在执行时分为三步:

  1. 为uniqueInstance分配空间;
  2. 初始化uniqueInstance;
  3. 将uniqueInstance指向分配的内存地址。

        采用volatile会禁止JVM的指令重排(指令重排会导致有些协程获取到还没有初始化的实例),保证多线程环境下的安全运行。

4. 饿汉式

        饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即:在编码时已经指明马上创建该对象,不需要等待调用时再创建。

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

5. 静态内部类

        当外部类Singleton被加载时,静态内部类SingletonHolder并没有被加载金内存,当调用getInstance()方法时,才会触发SingletonHolder.instance,此时静态内部类才会被加载进内存,并且初始化instance实例。该方法延迟了实例化,节约了资源,且线程安全,性能也提高了。

class Singleton{
    private Singleton(){}    
    
    // 静态内部类持有实例
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }
    
    // 公告静态方法,返回实例
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}; 

6. 枚举类

        默认枚举实例的创建就是线程安全的,且在任何情况下都是单例。可以防止调用者使用反射、序列化与反序列化机制强制生成多个单例对象,破坏单例模式。

public enum Singleton{
    INSTANCE;
    
    // 可以添加其他方法和属性
    public void doSomething(){
        // 实现...
    }
}

总结:

(1)单例模式常见的写法有两种:懒汉式、饿汉式

  • 懒汉式:在需要用到对象时才实例化对象,正确的实现方式是:Double Check + Lock,解决了并发安全和性能低下问题
  • 饿汉式:在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。

(2)在开发中如果对内存要求非常高,那么使用懒汉式写法,可以在特定时候才创建该对象;

        如果对内存要求不高使用饿汉式写法,因为简单不易出错,且没有任何并发安全和性能问题

(3)为了防止多线程环境下,因为指令重排序导致变量报NPE,需要在单例对象上添加volatile关键字防止指令重排序

(4)最优雅的实现方式是使用枚举,其代码精简,没有线程安全问题,且 Enum 类内部防止反射和反序列化时破坏单例。

三、应用场景

单例设计模式适用于以下⼀些场景:

  • 资源共享:当多个模块或系统需要共享某⼀资源时,可以使用单例模式确保该资源只被创建⼀次,避免重复创 建和浪费资源。
  • 控制资源访问:单例模式可以用于控制对特定资源的访问,例如数据库连接池、线程池等。
  • 配置管理器:当整个应用程序需要共享⼀些配置信息时,可以使用单例模式将配置信息存储在单例类中,方便全局访问和管理。
  • 日志记录器:单例模式可以用于创建⼀个全局的日志记录器,用于记录系统中的日志信息。
  • 线程池:在多线程环境下,使用单例模式管理线程池,确保线程池只被创建⼀次,提高线程池的利⽤率。
  • 缓存:单例模式可以⽤于实现缓存系统,确保缓存只有⼀个实例,避免数据不⼀致性和内存浪费。

相关推荐

  1. 模式】的实现方式总结

    2024-07-13 03:00:03       26 阅读
  2. 模式详解

    2024-07-13 03:00:03       56 阅读
  3. 模式详解

    2024-07-13 03:00:03       40 阅读
  4. 模式详解

    2024-07-13 03:00:03       29 阅读
  5. 模式详解

    2024-07-13 03:00:03       20 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-13 03:00:03       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-13 03:00:03       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-13 03:00:03       58 阅读
  4. Python语言-面向对象

    2024-07-13 03:00:03       69 阅读

热门阅读

  1. error: #29: expected an expression

    2024-07-13 03:00:03       17 阅读
  2. MySQL版本升级

    2024-07-13 03:00:03       17 阅读
  3. 数据建设实践之大数据平台(四)安装mysql

    2024-07-13 03:00:03       22 阅读
  4. Python-数据爬取(爬虫)

    2024-07-13 03:00:03       20 阅读
  5. 关于QT实现绘图库的技术栈考虑

    2024-07-13 03:00:03       20 阅读
  6. 使用Python绘制百分比堆积条形图

    2024-07-13 03:00:03       23 阅读
  7. How to Use shred to Erase a Drive or File in Fedora

    2024-07-13 03:00:03       22 阅读
  8. Postman接口测试工具详解

    2024-07-13 03:00:03       20 阅读
  9. 【题解】二分 | [USACO 2009 Dec S]Music Notes

    2024-07-13 03:00:03       20 阅读
  10. 如何在工作中"开悟"?

    2024-07-13 03:00:03       26 阅读
  11. 53.处理cpu的异常

    2024-07-13 03:00:03       22 阅读