【设计模式】单例模式(创建型)⭐⭐⭐

文章目录

  • 1.概念
    • [1.1 什么是单例模式](#1.1 什么是单例模式)
    • [1.2 优点与缺点](#1.2 优点与缺点)
  • 2.实现方式
    • [2.1 懒汉式](#2.1 懒汉式)
      • [2.1.1 懒汉式(线程不安全)](#2.1.1 懒汉式(线程不安全))
      • [2.1.2 懒汉式(线程安全)](#2.1.2 懒汉式(线程安全))
    • [2.2 饿汉式](#2.2 饿汉式)
    • [2.3 双重检查锁定(Double-Checked Locking)](#2.3 双重检查锁定(Double-Checked Locking))
    • [2.4 静态内部类](#2.4 静态内部类)
    • [2.5 枚举](#2.5 枚举)
  • [3 Java 哪些地方用到了单例模式](#3 Java 哪些地方用到了单例模式)
  • [4 Spring 哪些地方用到了单例模式](#4 Spring 哪些地方用到了单例模式)

1.概念

1.1 什么是单例模式

单例模式属于创建型模式,一个单例类在任何情况下都只存在一个实例

构造方法必须是私有的、由自己创建一个静态变量存储实例,对外提供一

个静态公有方法获取实例。

1.2 优点与缺点

优点 :是内存中只有一个实例,减少了开销,尤其是频繁创建和销毁实例的情况下,可以避免对资源的多重占用。
缺点 :没有抽象层,难以扩展,与单一职责原则冲突(单例模式由于其全局访问的特性,往往会使得类的使用变得非常广泛,这会导致类的职责膨胀,变得越来越难以维护。)。

2.实现方式

2.1 懒汉式

懒加载 (lazy loading):使用的时候再创建对象。

2.1.1 懒汉式(线程不安全)

在第一次调用获取实例的方法时才创建实例。由于没有同步措施,因此线程不安全。

java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

2.1.2 懒汉式(线程安全)

在懒汉式的基础上,通过同步代码块或方法来确保线程安全,但会降低性能。

java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

2.2 饿汉式

类加载时就创建好实例,避免了线程同步问题,但可能会导致资源浪费。

java 复制代码
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {}

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

2.3 双重检查锁定(Double-Checked Locking)

结合懒汉式和线程安全的特点,在获取实例时进行双重检查,以提高性能。

java 复制代码
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

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

volatile关键字的作用是确保instance变量的可见性和防止指令重排序。

2.4 静态内部类

利用Java的类加载机制来实现懒加载和线程安全。

java 复制代码
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

在这个示例中,SingletonHolder是一个静态内部类,它包含了一个静态的Singleton实例。SingletonHolder类只有在getInstance()方法被调用时才会被加载,这保证了Singleton实例的延迟加载。同时,由于类的加载过程是线程安全的,所以这种方式也保证了单例的线程安全。

2.5 枚举

使用枚举来实现单例模式,这是最简单的实现方式,并且天生线程安全,防止多次实例化。

java 复制代码
public enum Singleton {
    INSTANCE1,
    INSTANCE2;
    
    public void someMethod() {
        // 功能处理
        System.out.println("执行一些功能处理");
    }
}
java 复制代码
public class Main {
    public static void main(String[] args) {
        // 调用第一个枚举实例的方法
        Singleton.INSTANCE1.someMethod();
        
        // 调用第二个枚举实例的方法
        Singleton.INSTANCE2.someMethod();
    }
}

枚举天生线程安全的原因在于Java枚举的设计和实现:
枚举实例的创建时机 :枚举的实例是在类加载时创建的,这个过程是由Java虚拟机(JVM)在加载枚举类时自动完成的。JVM确保在任何线程可以访问枚举的实例之前,这些实例已经被创建并初始化完毕。
Java内存模型 (JMM):Java内存模型确保了所有线程可以看到枚举实例的创建和初始化状态。这意味着在任何线程可以访问枚举的实例之前,这些实例的状态对于所有线程都是可见的。
同步机制 :在枚举实例的创建过程中,JVM内部会使用同步机制来确保实例的创建是原子性的,也就是说,在创建枚举实例的过程中,不会有其他线程干扰。
不可变性 :枚举实例一旦被创建,就不能被修改或重新赋值。这意味着多个线程可以安全地访问枚举实例,而不会出现竞态条件或数据不一致的问题。

由于这些原因,枚举天生就是线程安全的,不需要开发者手动处理同步问题。

3 Java 哪些地方用到了单例模式

资源管理器 :如数据库连接池、缓存管理器等,通常需要全局访问点,并且在整个应用生命周期内只需要一个实例。
配置管理器 :用于加载和解析配置文件的类,通常设计为单例,以确保配置的全局一致性和访问的便捷性。
日志记录器 :如Log4j、SLF4J等日志框架,通常提供单例的日志记录器,以便在整个应用中统一记录日志。
服务定位器 (Service Locator):在某些设计模式中,服务定位器用于查找和存储服务对象,通常实现为单例。
打印机 spooler:在操作系统中,打印机管理器通常是单例的,以确保打印任务的有序处理。
线程池 :在多线程编程中,线程池通常设计为单例,以避免创建多个线程池的资源浪费。
Web应用中的共享资源 :如在Servlet中,一个ServletContextListener可以用来创建单例资源,并在整个Web应用中共享。
应用上下文 :在Spring框架中,ApplicationContext本身就是一个单例,用于管理Spring beans。
工厂类 :在设计模式中,工厂类通常设计为单例,以确保对象创建的统一性和便捷性。
窗口管理器 :在图形用户界面(GUI)应用中,窗口管理器通常是单例的,以管理所有窗口和对话框。
驱动程序管理器 :如JDBC的DriverManager类,它负责管理数据库驱动程序,通常是单例的。
应用级的状态管理:如用户会话管理、应用级别的缓存等,通常需要全局访问点,并且只有一个实例。

4 Spring 哪些地方用到了单例模式

Spring Bean的默认作用域 :在Spring中,当你定义一个bean时,如果没有指定它的作用域,它默认是单例的。这意味着在整个Spring IoC容器中,这个bean只有一个实例。这是最常见的单例模式使用场景。
Spring的ApplicationContext :Spring的应用上下文(ApplicationContext)自身就是一个单例。在整个应用生命周期内,通常只有一个ApplicationContext实例,它负责管理所有的Spring beans。
Spring的事件发布机制 :Spring的事件发布机制使用单例模式来确保事件的多播器(ApplicationEventMulticaster)是单例的。这样可以确保事件在整个应用中被统一管理和分发。
数据源(DataSource) :在Spring中配置数据源时,通常会将其配置为单例,以确保整个应用共享同一个数据库连接池。
事务管理器(TransactionManager) :事务管理器通常也是单例的,以确保整个应用中的事务可以被统一管理。
Spring Security的SecurityContextHolder :Spring Security使用SecurityContextHolder来持有安全上下文信息(Spring Security提供了一套全面的安全服务,包括认证、授权),它是单例的,可以在整个应用中访问。
缓存管理器(CacheManager) :在使用Spring的缓存抽象时,缓存管理器通常是单例的,以确保缓存策略在整个应用中一致。
任务调度器(TaskScheduler) :Spring的任务调度器通常也是单例的,以确保定时任务和异步任务的统一调度。
消息服务(MessageService ):在使用Spring集成或Spring Boot与消息中间件集成时,消息服务通常是单例的,以确保消息处理的一致性。
配置文件处理:Spring的Environment抽象和PropertySources是单例的,它们负责加载和解析配置文件,确保配置在整个应用中是一致的。

相关推荐
Jabes.yang14 分钟前
Java求职面试: 互联网医疗场景中的缓存技术与监控运维应用
java·redis·spring security·grafana·prometheus·oauth2·互联网医疗
li星野24 分钟前
打工人日报#20251011
笔记·程序人生·fpga开发·学习方法
QT 小鲜肉28 分钟前
【个人成长笔记】在Ubuntu中的Linux系统安装 anaconda 及其相关终端命令行
linux·笔记·深度学习·学习·ubuntu·学习方法
初级炼丹师(爱说实话版)29 分钟前
内存泄漏与内存溢出
java
QT 小鲜肉30 分钟前
【个人成长笔记】在Ubuntu中的Linux系统安装实验室WIFI驱动安装(Driver for Linux RTL8188GU)
linux·笔记·学习·ubuntu·学习方法
CryptoRzz38 分钟前
越南k线历史数据、IPO新股股票数据接口文档
java·数据库·后端·python·区块链
学Java的bb1 小时前
MybatisPlus
java·开发语言·数据库
讓丄帝愛伱1 小时前
Mybatis Log Free插件使用
java·开发语言·mybatis
重生之我要当java大帝1 小时前
java微服务-尚医通-编写医院设置接口上
java·数据库·微服务
夫唯不争,故无尤也1 小时前
Tomcat 内嵌启动时找不到 Web 应用的路径
java·前端·tomcat