设计模式之单例模式

一、定义

单例模式(Singleton Pattern)是设计模式中的一种创建型模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

主要特点如下:

  • 唯一性:单例模式保证一个类在内存中只有一个实例存在。这意味着无论何时何地访问这个类,都只会获取到同一个实例。
  • 全局访问点:单例模式提供一个全局访问点,通常是通过一个静态方法来获取实例。这个方法确保每次调用都返回同一个实例。
  • 线程安全:在多线程环境下,单例模式需要确保线程安全,以避免多个线程同时创建多个实例。

二、应用场景

2.1 数据库连接池管理

在 Web 应用中,频繁地创建和销毁数据库连接是非常耗费资源的操作。使用单例模式创建数据库连接池可以有效地解决这个问题。

例如,在 Java 的 Web 应用中,像 Apache Commons DBCP(Database Connection Pool)或者 C3P0 这样的连接池库,它们在内部通常会使用单例模式来管理连接池。整个 Web 应用共享一个连接池实例,当有多个请求需要访问数据库时,从这个连接池中获取连接,而不是每次都重新创建一个新的连接。这样可以显著提高数据库访问的性能和效率,同时还能更好地控制数据库连接的数量,避免数据库因为过多的连接请求而崩溃。

2.2 配置信息管理

Web 应用通常有很多配置信息,如数据库配置(数据库名称、用户名、密码等)、服务器配置(端口号、IP 地址等)、应用程序相关的业务配置(如缓存过期时间、文件上传路径等)。

采用单例模式的配置管理类可以确保整个 Web 应用使用的是同一套配置信息。例如,在一个基于 Spring 框架的 Web 应用中,@ConfigurationProperties注解可以用于将配置文件中的属性绑定到一个 Java 类上,这个类通常可以设计成单例模式。这样,无论在 Web 应用的哪个模块(如控制器、服务层、数据访问层)需要使用配置信息,都可以通过访问这个单例的配置管理类来获取,保证了配置信息的一致性和全局可用性。

2.3 日志记录器

对于 Web 应用的日志记录,只需要一个统一的日志记录器实例就可以满足需求。

以 Log4j 或 Logback 等日志框架为例,它们在 Web 应用中的使用通常是基于单例模式的。这个单例的日志记录器可以记录来自不同用户请求的日志信息,包括访问的 URL、请求参数、处理时间等。例如,当一个用户请求访问一个 Web 页面时,在控制器层可以通过单例的日志记录器记录请求的开始时间;在服务层处理业务逻辑时,可以记录业务处理的相关信息;在数据访问层访问数据库时,也可以记录数据库操作的情况。所有这些日志信息都通过同一个日志记录器记录,方便了后续的日志查看和问题排查,如查找性能瓶颈或者错误发生的原因。

2.4 缓存管理

在 Web 应用中,为了提高性能,经常会使用缓存来存储一些频繁访问的数据。

单例模式的缓存管理类可以确保整个 Web 应用共享同一个缓存实例。例如,一个电子商务网站可以使用单例的缓存来存储热门商品的信息。当多个用户同时访问热门商品页面时,直接从缓存中获取商品信息,而不是每次都从数据库中查询,大大提高了响应速度。而且,单例模式可以方便地对缓存进行统一的管理,如设置缓存过期策略、清除缓存等操作。

三、实现方式

3.1 饿汉式

通过静态变量持有单例实例,并在类加载时初始化创建实例。由于类加载时是线程安全的,因此该实现方式天生是线程安全的。

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

    // 私有构造方法,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

优点

实现简单,代码易读。线程安全,因为实例在类加载时创建。

缺点

可能造成资源浪费,如果该单例类在程序运行过程中从未被使用,则会浪费内存。类加载时即创建实例,无法实现延迟加载。

适用场景

适用于单例类较轻量级且在程序中必定会使用的情况。

3.2 懒汉式

在第一次使用时创建实例,而不是在类加载时。通过检查实例是否为null来决定是否创建实例。

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

    // 私有构造方法,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

优点

实现简单,延迟加载,只有在需要时才创建实例。

缺点

线程不安全,多个线程同时调用getInstance方法时可能会创建多个实例。

适用场景

适用于单线程环境或不需要考虑线程安全的情况。

3.3 线程安全的懒汉式

通过在getInstance方法上加同步锁,确保线程安全。每次调用getInstance方法时都会进行同步,确保只有一个线程能创建实例。

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

    // 私有构造方法,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

优点

线程安全,确保只有一个实例。

缺点

每次调用getInstance方法时都需要进行同步,性能较差。

适用场景

适用于多线程环境,但对性能要求不高的情况。

3.4 双重检查锁方式

结合懒汉式和线程安全的优点,通过双重检查锁机制实现线程安全的延迟加载。

第一次检查实例是否为null,如果为null则进入同步块,再次检查是否为null,然后创建实例。注意这种方式需要使用volatile关键字来确保对象的可见性。

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关键字来确保可见性和有序性。

适用场景

适用于多线程环境,对性能要求较高的情况。

3.5 静态内部类方式

利用JVM的类加载机制实现延迟加载和线程安全。静态内部类在外部类被加载时并不会立即加载,只有在调用getInstance方法时才会加载静态内部类,从而创建实例。

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

    // 静态内部类
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 提供全局访问点
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

优点

线程安全,延迟加载,性能优良。实现简单易懂。

缺点

无明显缺点,是一种推荐的单例实现方式。

适用场景

适用于大多数单例场景,特别是需要延迟加载和线程安全的情况。

相关推荐
xmh-sxh-1314几秒前
jdk各个版本介绍
java
天天扭码20 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶20 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺25 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序32 分钟前
vue3 封装request请求
java·前端·typescript·vue
陈王卜1 小时前
django+boostrap实现发布博客权限控制
java·前端·django
小码的头发丝、1 小时前
Spring Boot 注解
java·spring boot
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
飞滕人生TYF1 小时前
java Queue 详解
java·队列
武子康1 小时前
大数据-230 离线数仓 - ODS层的构建 Hive处理 UDF 与 SerDe 处理 与 当前总结
java·大数据·数据仓库·hive·hadoop·sql·hdfs