设计模式之单例模式

一、定义

单例模式(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;
    }
}

优点

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

缺点

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

适用场景

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

相关推荐
Algorithm15763 分钟前
JVM是什么,与Java的关系是什么,以及JVM怎么实现的跨平台性
java·开发语言·jvm
遇见你真好。1 小时前
SpringBoot整合quartz定时任务
java·springboot·quartz
一颗甜苞谷1 小时前
开源一款基于 JAVA 的仓库管理系统,支持三方物流和厂内物流,包含 PDA 和 WEB 端的源码
java·开发语言·开源
攻心的子乐1 小时前
idea使用svn
java·ide·intellij-idea
程序员大佬超1 小时前
IDEA解决 properties 文件乱码问题
java·ide·intellij-idea
秋恬意1 小时前
LinkedList 源码分析
java·开发语言·面试
隔窗听雨眠1 小时前
深入理解Redis的四种模式
java·redis·mybatis
lexusv8ls600h1 小时前
微服务设计模式 - 重试模式(Retry Pattern)
java·spring boot·微服务
Viktor_Ye2 小时前
高效数据集成:易仓退款订单与金蝶销售退货单的自动化对接
java·运维·自动化
feilieren2 小时前
leetcode - 684. 冗余连接
java·开发语言·算法