java设计模式 单例模式

单例模式

什么是单例模式

单例模式(Singleton Pattern)是一种设计模式,目的是确保一个类在整个程序中只有一个实例,并提供一个全局的访问点来获取该实例。它常用于需要频繁访问某个对象的场景,且该对象在整个程序运行过程中只需一个实例时。

单例模式的用途

单例模式的主要用途包括:

控制资源共享 :在许多应用场景下,某个类的实例可能是资源密集型的或占用大量内存的,单例模式确保只有一个实例存在,避免了不必要的资源浪费。
全局访问点 :它为应用程序中的某些全局状态提供了一个访问点,例如,数据库连接池、日志管理器、配置管理等。
跨多个组件共享数据或状态 :通过单例模式,所有组件可以访问到相同的实例,确保数据一致性和共享状态。
避免重复创建:对于一些创建成本较高的对象,可以确保只创建一次并重复使用,降低了程序的开销。

单例模式的写法

饿汉式

饿汉式是类加载时就创建实例,不论是否使用,通常适用于实例化过程简单且没有线程安全问题的情况。

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

    private Singleton() {}  // 私有构造函数,防止外部创建实例

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

懒汉式

懒汉式是在第一次使用时才创建实例,延迟加载,但它的缺点是线程不安全,在多线程环境中可能会出问题。

例如多个线程获取这个单例对象的时候会先执行static静态方法,当多个线程同时进入getInstance方法因为没加锁,所以可能创建多个Singleton对象

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

    private Singleton() {}  // 私有构造函数,防止外部创建实例

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
线程安全的懒汉式

通过加锁确保线程安全。虽然可以解决多线程问题,但由于每次获取实例时都要进行同步,可能会导致性能瓶颈。

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

    private Singleton() {}  // 私有构造函数,防止外部创建实例

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
双重检验锁

双重检查锁定是懒汉式的优化版本,只有第一次创建实例时加锁,后续访问时不再加锁,避免了性能问题。

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;
    }
}
静态内部类

静态内部类是线程安全的懒加载方式,它利用 Java 类加载机制确保线程安全,并且延迟加载

java 复制代码
public class Singleton {
    private Singleton() {}  // 私有构造函数,防止外部创建实例

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

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

解释:

SingletonHelper 类是静态的,并且只会在 getInstance() 方法第一次调用时加载,因此确保了延迟加载和线程安全。

这种方式不仅线程安全,而且性能较高,是推荐的单例模式实现方法。

由于类加载的过程是线程安全的,JVM 会保证只会有一个线程加载类,并且保证初始化过程不会被多次执行。

单例模式的应用场景

单例模式的应用非常广泛,常见的地方包括:

数据库连接池:为了提高数据库连接的复用性,避免频繁创建和销毁数据库连接对象,数据库连接池通常会使用单例模式。

日志管理器:日志对象通常是单一的,不需要多个实例来记录日志,单例模式确保日志管理器在整个程序中只有一个实例。

配置管理:应用程序的配置通常是唯一的,使用单例模式来读取和管理配置文件,避免多次加载配置。

线程池管理:为了避免多次创建线程池,线程池管理对象通常使用单例模式来保证系统中只存在一个线程池实例。

缓存管理:许多应用需要使用缓存来提高性能,缓存对象可以使用单例模式,保证缓存数据一致性。

应用程序上下文(AppContext):有些框架或大型系统中,常用单例模式管理应用程序的上下文信息,如Spring框架中的应用上下文。

在我们使用的Springboot框架中,springboot中的注解例如@Controller @Service @Repository都是spring容器给我们管理创建的饿汉式单例对象

数据库连接池

在 Spring Boot 中,数据库连接池(如 HikariCP,默认的连接池)通过单例模式来管理的。在 application.yml 或 application.properties 文件中配置数据库连接池的属性时,Spring Boot 会将这些配置应用到数据库连接池的实例中。

Spring Boot 会在启动时初始化数据库连接池,并且通常会作为一个 单例 Bean 存在,避免了每次需要连接数据库时都重新创建连接池的开销。这个单例的数据库连接池会被应用程序的所有数据库操作共享,从而提高了数据库连接的复用性和效率。

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver  # 这个通常是可以省略的,Spring Boot 会自动推断
    hikari:
      maximum-pool-size: 10  # 连接池配置

Spring Boot 会根据这些配置自动创建并初始化数据库连接池,并将其作为单例 Bean 注入到 Spring 容器中。

日志配置

在 Spring Boot 中,日志对象(如 Logger)是通过 LoggerFactory 来创建的,且通常是单例的。日志管理器是一个典型的应用场景,其中日志对象不需要多个实例,因此使用单例模式来确保在整个应用程序中只有一个实例。

配置管理类

Spring Boot 会根据配置文件中的内容自动创建一个配置类,加载配置并将其作为一个单例 Bean 提供给应用程序。

在 Spring Boot 启动时,DataSourceConfig 会作为一个单例 Bean 被加载,并且能够共享整个应用的数据库配置。

java 复制代码
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceConfig {
    private String url;
    private String username;
    private String password;
    // getters and setters
}
相关推荐
头发还在的女程序员9 小时前
医院陪诊管理系统怎么选择?——2026 年选型避坑与架构参考
java·开发语言·陪诊系统·陪诊app·医院陪诊陪护
CodeStats10 小时前
【Spring事务】Spring事务注解 @Transactional 完整体系:从 MySQL 隔离级别到 MyBatis 原理详解
java·spring·mybatis·事务·transactional
我命由我1234510 小时前
Android 开发问题:为 PDFView 设置一个带有黑色边框的背景 drawable,但边框没有生效
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
AI大法师11 小时前
一个机场如何被做成 IP 场景:宝可梦机场的设计方法总结
大数据·人工智能·设计模式·新媒体运营
都叫我大帅哥12 小时前
从Python到Java:为什么企业级Agent最终会选择Java?
java·ai编程
wanderist.13 小时前
Lambda表达式在算法竞赛中的应用
java·开发语言·算法
腻害兔14 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:IM 即时通讯模块,一个被低估的「全功能聊天系统」
java·前端·vue.js·产品经理·ai编程
心念枕惊14 小时前
.NET CORE 授权进阶-角色、策略与动态权限实现
java·前端·.netcore
晚安code14 小时前
干掉成山的 if-else:工厂造、策略选,一文讲透两个模式的配合
后端·设计模式
景同学16 小时前
把 AI 用到线上运维:可行、有效,前提是喂足信息——一次 Full GC 排障实录
java·人工智能·后端