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

文章目录

  • 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是单例的,它们负责加载和解析配置文件,确保配置在整个应用中是一致的。

相关推荐
哪 吒27 分钟前
最简单的设计模式,抽象工厂模式,是否属于过度设计?
设计模式·抽象工厂模式
Theodore_102227 分钟前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸1 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象2 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了2 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
小二·3 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic3 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
懒洋洋大魔王3 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq
武子康3 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神4 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式