目录
3. 使用 ThreadPoolTaskScheduler(Spring 内置)
单例模式(Singleton Pattern)是一种创建型设计模式,其核心目标是确保一个类只有一个实例存在,并提供全局唯一的访问点。它常用于资源控制(如数据库连接池、线程池)、配置管理、日志对象等场景。
核心特性
- 唯一性:无论调用多少次,始终返回同一个对象实例。
- 全局访问:通过静态方法或静态变量提供全局访问入口。
- 私有构造:禁止通过
new
关键字直接实例化。
实现方式详解
以下是单例模式的几种典型实现(以 Java 为例):
1. 饿汉式(Eager Initialization)
特点:类加载时立即初始化实例,线程安全但可能浪费资源。
public class EagerSingleton {
// 类加载时直接初始化实例
private static final EagerSingleton instance = new EagerSingleton();
// 私有构造方法
private EagerSingleton() {}
// 全局访问点
public static EagerSingleton getInstance() {
return instance;
}
}
2. 懒汉式(Lazy Initialization)
特点:第一次调用时创建实例,需解决线程安全问题。
- 基础版(非线程安全):
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
- 线程安全版(双重检查锁,Double-Checked Locking):
public class ThreadSafeSingleton {
// volatile 防止指令重排序
private static volatile ThreadSafeSingleton instance;
private ThreadSafeSingleton() {}
public static ThreadSafeSingleton getInstance() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
3. 静态内部类(Bill Pugh 实现)
特点:利用类加载机制保证线程安全,延迟加载且无锁。
public class InnerClassSingleton {
private InnerClassSingleton() {}
// 静态内部类在调用 getInstance() 时才会加载
private static class SingletonHolder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4. 枚举(Enum)
特点:天然线程安全,防止反射攻击,简洁高效(推荐方式)。
public enum EnumSingleton {
INSTANCE; // 单例实例
public void doSomething() {
// 业务逻辑
}
}
// 使用:EnumSingleton.INSTANCE.doSomething();
破坏单例的场景及防御
- 反射攻击:通过反射调用私有构造方法创建新实例。
-
- 防御:在构造方法中检查实例是否已存在,若存在则抛出异常。
- 反序列化:反序列化可能生成新对象。
-
- 防御:实现
readResolve()
方法返回已有实例。
- 防御:实现
- 克隆:重写
clone()
方法并抛出异常。
Spring 中的单例
在 Spring 框架中,默认的 Bean 作用域是单例(@Scope("singleton")
),但需注意:
- Spring 单例是容器级别的:每个容器中保证唯一,而非 JVM 级别。
- 非严格单例:如果通过
new
或反射创建,仍可能生成多个实例。
适用场景
- 频繁使用的对象(减少资源开销)。
- 需严格控全局状态的场景(如计数器、配置管理)。
- 共享资源访问(如文件系统、数据库连接池)。
缺点
- 违反单一职责原则(同时管理实例化和业务逻辑)。
- 可能隐藏代码耦合。
- 多线程需额外注意同步问题。
单例模式需要谨慎使用,避免滥用导致代码难以测试和维护。以下是不同解决方法的好处及具体场景示例:
优点
1. 手动注册 Bean(代码配置)
好处:完全控制线程池参数(核心线程数、队列容量等),适合需要定制化配置的场景。
@Configuration
public class SchedulerConfig {
@Bean("scheduledExecutorService")
public ScheduledExecutorService scheduledExecutorService() {
return Executors.newScheduledThreadPool(5);
}
}
示例场景:
你需要一个固定大小为5的线程池,并明确指定线程名称格式:
@Bean("scheduledExecutorService")
public ScheduledExecutorService customExecutor() {
return new ScheduledThreadPoolExecutor(5,
new ThreadFactoryBuilder().setNameFormat("scheduler-%d").build());
}
2. 使用 Spring Boot 自动配置
好处:无需手动编码,通过配置文件快速启用调度功能,简化配置。
# application.properties
spring.task.scheduling.pool.size=5
示例场景:
简单应用需要快速启用定时任务,只需在 @Scheduled
注解方法上使用:
@Scheduled(fixedRate = 5000)
public void task() {
System.out.println("Task executed by auto-configured scheduler");
}
3. 使用 ThreadPoolTaskScheduler
(Spring 内置)
好处:与 Spring 生态集成更好,支持更丰富的配置(错误处理、线程管理等)。
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("spring-scheduler-");
return scheduler;
}
示例场景:
需要为不同的任务分配独立的调度器,或在任务失败时记录日志:
@Bean
public ThreadPoolTaskScheduler paymentScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(3);
scheduler.setErrorHandler(t -> logger.error("Payment task failed", t));
return scheduler;
}
4. 检查包扫描路径
好处:避免因配置类未被扫描导致的 Bean 未注册问题。
示例:
假设主启动类在 com.example.app
包下,而配置类在 com.example.config
包中:
@SpringBootApplication
@ComponentScan("com.example.config") // 显式扫描配置包
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
常见误区示例
问题:Bean 名称拼写错误导致依赖注入失败。
// ❌ 错误:Bean 名称写成了 "scheduledExecutor"
@Bean("scheduledExecutor")
public ScheduledExecutorService executor() { ... }
// ❌ 使用时名称不一致
@Autowired
@Qualifier("scheduledExecutorService") // 找不到 Bean!
private ScheduledExecutorService executor;