单例模式(Singleton Pattern)是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点来获取该实例。
使用场景
- 一般情况下:推荐使用静态内部类实现,平衡了各种需求
- 对安全性要求极高:使用枚举实现
- 需要传递初始化参数:使用双重检查锁定
- 资源占用小且启动时就需要:使用饿汉式
- 简单场景且对性能要求不高:使用基本懒汉式
SpringBoot项目中Demo
单例模式 - 配置管理器(枚举实现方式)
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 单例模式 - 配置管理器(枚举实现方式)
*
* 符合阿里巴巴规范的单例实现:
* 1. 使用枚举实现单例,这是《Effective Java》推荐的实现方式
* 2. 自动处理序列化/反序列化问题
* 3. 线程安全,由JVM保证
* 4. 防止反射攻击
*
* @author demo
* @since 1.0.0
*/
public enum ConfigManagerSingleton {
/**
* 单例实例
*/
INSTANCE;
/**
* 使用ConcurrentHashMap保证线程安全
*/
private final Map<String, String> configMap;
/**
* 枚举的构造方法在类加载时自动调用
*/
ConfigManagerSingleton() {
this.configMap = new ConcurrentHashMap<>();
initDefaultConfig();
}
/**
* 初始化默认配置
*/
private void initDefaultConfig() {
configMap.put("app.name", "demo-application");
configMap.put("app.version", "1.0.0");
configMap.put("app.timezone", "Asia/Shanghai");
}
/**
* 获取配置值
*
* @param key 配置键
* @return 配置值,如果不存在则返回null
*/
public String getConfig(String key) {
return configMap.get(key);
}
/**
* 获取配置值,提供默认值
*
* @param key 配置键
* @param defaultValue 默认值
* @return 配置值,如果不存在则返回默认值
*/
public String getConfig(String key, String defaultValue) {
return configMap.getOrDefault(key, defaultValue);
}
/**
* 设置配置值
*
* @param key 配置键
* @param value 配置值
*/
public void setConfig(String key, String value) {
if (key == null || key.trim().isEmpty()) {
throw new IllegalArgumentException("配置键不能为空");
}
configMap.put(key, value);
}
/**
* 移除配置
*
* @param key 配置键
* @return 被移除的值,如果不存在则返回null
*/
public String removeConfig(String key) {
return configMap.remove(key);
}
/**
* 获取所有配置
*
* @return 配置的副本,防止外部修改内部数据
*/
public Map<String, String> getAllConfigs() {
return new ConcurrentHashMap<>(configMap);
}
/**
* 清空所有配置(仅用于测试场景)
*/
public void clearAll() {
configMap.clear();
}
/**
* 获取配置数量
*
* @return 配置数量
*/
public int size() {
return configMap.size();
}
}
单例模式 - 配置管理器(枚举实现方式)单元测试
import org.junit.jupiter.api.Test;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
/**
* 配置管理器单例测试类
*
* 测试枚举实现的单例模式的正确性、线程安全性以及功能完整性
*
* @author demo
* @since 1.0.0
*/
class ConfigManagerSingletonTest {
/**
* 测试单例实例的唯一性
*/
@Test
void testSingletonInstanceUniqueness() {
ConfigManagerSingleton instance1 = ConfigManagerSingleton.INSTANCE;
ConfigManagerSingleton instance2 = ConfigManagerSingleton.INSTANCE;
assertSame(instance1, instance2, "枚举单例实例应该是唯一的");
assertEquals(instance1.hashCode(), instance2.hashCode(), "相同实例的哈希码应该相同");
}
/**
* 测试获取配置
*/
@Test
void testGetConfig() {
ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
String appName = configManager.getConfig("app.name");
assertNotNull(appName, "app.name配置应该存在");
assertEquals("demo-application", appName, "app.name的值应该是demo-application");
String notExistKey = configManager.getConfig("not.exist.key");
assertNull(notExistKey, "不存在的配置键应该返回null");
}
/**
* 测试获取配置带默认值
*/
@Test
void testGetConfigWithDefaultValue() {
ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
String defaultValue = configManager.getConfig("not.exist.key", "default-value");
assertEquals("default-value", defaultValue, "不存在的配置键应该返回默认值");
String existValue = configManager.getConfig("app.name", "default-value");
assertEquals("demo-application", existValue, "存在的配置键应该返回实际值");
}
/**
* 测试设置配置
*/
@Test
void testSetConfig() {
ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
configManager.setConfig("test.key", "test-value");
String value = configManager.getConfig("test.key");
assertEquals("test-value", value, "设置的配置值应该正确");
// 清理测试数据
configManager.removeConfig("test.key");
}
/**
* 测试设置空键的异常
*/
@Test
void testSetConfigWithEmptyKey() {
ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
assertThrows(IllegalArgumentException.class, () -> {
configManager.setConfig(null, "value");
}, "设置null键应该抛出异常");
assertThrows(IllegalArgumentException.class, () -> {
configManager.setConfig("", "value");
}, "设置空字符串键应该抛出异常");
assertThrows(IllegalArgumentException.class, () -> {
configManager.setConfig(" ", "value");
}, "设置空白键应该抛出异常");
}
/**
* 测试移除配置
*/
@Test
void testRemoveConfig() {
ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
configManager.setConfig("test.remove.key", "test-value");
String removedValue = configManager.removeConfig("test.remove.key");
assertEquals("test-value", removedValue, "移除的值应该正确");
String afterRemove = configManager.getConfig("test.remove.key");
assertNull(afterRemove, "移除后应该返回null");
String removeNotExist = configManager.removeConfig("not.exist.key");
assertNull(removeNotExist, "移除不存在的键应该返回null");
}
/**
* 测试获取所有配置
*/
@Test
void testGetAllConfigs() {
ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
Map<String, String> configs = configManager.getAllConfigs();
assertNotNull(configs, "配置Map不应该为null");
assertTrue(configs.containsKey("app.name"), "应该包含app.name配置");
assertTrue(configs.containsKey("app.version"), "应该包含app.version配置");
// 验证返回的是副本
configs.put("test.key", "test-value");
assertFalse(configManager.getAllConfigs().containsKey("test.key"),
"修改返回的Map不应该影响原始配置");
}
/**
* 测试配置数量
*/
@Test
void testSize() {
ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
int originalSize = configManager.size();
assertTrue(originalSize > 0, "默认配置数量应该大于0");
configManager.setConfig("test.size.key", "test-value");
assertEquals(originalSize + 1, configManager.size(), "添加配置后数量应该增加");
configManager.removeConfig("test.size.key");
assertEquals(originalSize, configManager.size(), "移除配置后数量应该恢复");
}
/**
* 测试清空所有配置
*/
@Test
void testClearAll() {
ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
int originalSize = configManager.size();
configManager.setConfig("test.clear.key", "test-value");
configManager.clearAll();
assertEquals(0, configManager.size(), "清空后配置数量应该是0");
// 恢复默认配置
configManager.setConfig("app.name", "demo-application");
configManager.setConfig("app.version", "1.0.0");
configManager.setConfig("app.timezone", "Asia/Shanghai");
}
/**
* 测试线程安全性 - 多线程并发读写配置
*/
@Test
void testThreadSafety() throws InterruptedException {
ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
configManager.clearAll();
int threadCount = 50;
int operationsPerThread = 100;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(threadCount);
AtomicInteger successCount = new AtomicInteger(0);
for (int i = 0; i < threadCount; i++) {
final int threadId = i;
executorService.submit(() -> {
try {
startLatch.await();
for (int j = 0; j < operationsPerThread; j++) {
String key = "thread-" + threadId + "-key-" + j;
String value = "value-" + j;
configManager.setConfig(key, value);
String retrieved = configManager.getConfig(key);
if (value.equals(retrieved)) {
successCount.incrementAndGet();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
fail("线程被中断");
} finally {
endLatch.countDown();
}
});
}
startLatch.countDown();
endLatch.await();
executorService.shutdown();
assertEquals(threadCount * operationsPerThread, successCount.get(),
"所有读写操作都应该成功");
// 清理测试数据
configManager.clearAll();
configManager.setConfig("app.name", "demo-application");
configManager.setConfig("app.version", "1.0.0");
configManager.setConfig("app.timezone", "Asia/Shanghai");
}
}
单例模式 - ID生成器
import java.util.concurrent.atomic.AtomicLong;
/**
* 单例模式 - ID生成器
*
* 符合阿里巴巴规范的生产级单例实现:
* 1. 使用双重检查锁定(Double-Checked Locking)实现懒加载
* 2. 使用volatile关键字防止指令重排序
* 3. 使用AtomicLong保证线程安全
* 4. 提供清晰的文档注释
*
* @author demo
* @since 1.0.0
*/
public final class IdGeneratorSingleton {
/**
* 使用volatile关键字防止指令重排序,保证多线程环境下的可见性
* 符合阿里巴巴规范:必须使用volatile修饰单例实例
*/
private static volatile IdGeneratorSingleton instance;
/**
* 使用AtomicLong保证原子性操作,避免使用synchronized带来的性能问题
*/
private final AtomicLong counter = new AtomicLong(0);
/**
* 私有构造方法,防止外部实例化
* 符合阿里巴巴规范:单例类必须使用private构造方法
*/
private IdGeneratorSingleton() {
// 初始化逻辑
if (instance != null) {
throw new IllegalStateException("单例类不允许通过反射创建实例");
}
}
/**
* 获取单例实例
*
* 使用双重检查锁定(Double-Checked Locking):
* 1. 第一次检查:避免不必要的同步
* 2. 同步块:保证线程安全
* 3. 第二次检查:防止多个线程同时通过第一次检查
*
* @return 单例实例
*/
public static IdGeneratorSingleton getInstance() {
if (instance == null) {
synchronized (IdGeneratorSingleton.class) {
if (instance == null) {
instance = new IdGeneratorSingleton();
}
}
}
return instance;
}
/**
* 生成下一个ID
*
* @return 下一个ID值
*/
public long nextId() {
return counter.incrementAndGet();
}
/**
* 获取当前ID计数器值
*
* @return 当前计数器值
*/
public long getCurrentValue() {
return counter.get();
}
/**
* 重置计数器(仅用于测试场景)
*
* @param newValue 重置后的值
*/
public void reset(long newValue) {
counter.set(newValue);
}
/**
* 防止通过反序列化创建新实例
*
* @return 已存在的单例实例
*/
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("单例类不允许被克隆");
}
}
单例模式 - ID生成器 单元测试
import org.junit.jupiter.api.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
/**
* ID生成器单例测试类
*
* 测试单例模式的线程安全性、懒加载特性以及功能正确性
*
* @author demo
* @since 1.0.0
*/
class IdGeneratorSingletonTest {
/**
* 测试单例实例的唯一性
*/
@Test
void testSingletonInstanceUniqueness() {
IdGeneratorSingleton instance1 = IdGeneratorSingleton.getInstance();
IdGeneratorSingleton instance2 = IdGeneratorSingleton.getInstance();
assertSame(instance1, instance2, "多次获取的实例应该是同一个对象");
assertEquals(instance1.hashCode(), instance2.hashCode(), "相同实例的哈希码应该相同");
}
/**
* 测试ID生成功能
*/
@Test
void testIdGeneration() {
IdGeneratorSingleton idGenerator = IdGeneratorSingleton.getInstance();
idGenerator.reset(0);
long id1 = idGenerator.nextId();
long id2 = idGenerator.nextId();
long id3 = idGenerator.nextId();
assertEquals(1, id1, "第一个ID应该是1");
assertEquals(2, id2, "第二个ID应该是2");
assertEquals(3, id3, "第三个ID应该是3");
}
/**
* 测试重置功能
*/
@Test
void testReset() {
IdGeneratorSingleton idGenerator = IdGeneratorSingleton.getInstance();
idGenerator.reset(100);
long id1 = idGenerator.nextId();
assertEquals(101, id1, "重置后的第一个ID应该是101");
idGenerator.reset(0);
assertEquals(0, idGenerator.getCurrentValue(), "重置后当前值应该是0");
}
/**
* 测试线程安全性 - 多线程并发获取ID
*
* 测试在多线程环境下,ID生成器是否能正确工作且不产生重复ID
*/
@Test
void testThreadSafety() throws InterruptedException {
IdGeneratorSingleton idGenerator = IdGeneratorSingleton.getInstance();
idGenerator.reset(0);
int threadCount = 100;
int idsPerThread = 100;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(threadCount);
AtomicInteger[] results = new AtomicInteger[threadCount * idsPerThread];
AtomicInteger index = new AtomicInteger(0);
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
startLatch.await(); // 等待所有线程准备就绪
for (int j = 0; j < idsPerThread; j++) {
long id = idGenerator.nextId();
results[index.getAndIncrement()] = new AtomicInteger((int) id);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
fail("线程被中断");
} finally {
endLatch.countDown();
}
});
}
startLatch.countDown(); // 开始测试
endLatch.await(); // 等待所有线程完成
executorService.shutdown();
// 验证生成的ID数量
assertEquals(threadCount * idsPerThread, idGenerator.getCurrentValue(),
"生成的ID总数应该等于线程数 * 每个线程生成的ID数");
// 验证ID没有重复
boolean[] seen = new boolean[threadCount * idsPerThread + 1];
for (int i = 0; i < results.length; i++) {
int id = results[i].get();
assertTrue(id >= 1 && id <= threadCount * idsPerThread,
"ID应该在有效范围内: " + id);
assertFalse(seen[id], "ID不应该重复: " + id);
seen[id] = true;
}
}
/**
* 测试多线程环境下单例实例的唯一性
*/
@Test
void testSingletonInstanceInMultiThread() throws InterruptedException {
int threadCount = 100;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(threadCount);
IdGeneratorSingleton[] instances = new IdGeneratorSingleton[threadCount];
for (int i = 0; i < threadCount; i++) {
final int index = i;
executorService.submit(() -> {
try {
startLatch.await();
instances[index] = IdGeneratorSingleton.getInstance();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
fail("线程被中断");
} finally {
endLatch.countDown();
}
});
}
startLatch.countDown();
endLatch.await();
executorService.shutdown();
// 验证所有线程获取的实例都是同一个
IdGeneratorSingleton firstInstance = instances[0];
for (int i = 1; i < threadCount; i++) {
assertSame(firstInstance, instances[i],
"线程 " + i + " 获取的实例应该与第一个线程相同");
}
}
/**
* 测试克隆保护
*/
@Test
void testCloneNotSupportedException() {
IdGeneratorSingleton instance = IdGeneratorSingleton.getInstance();
assertThrows(CloneNotSupportedException.class, () -> {
instance.clone();
}, "单例类不应该支持克隆");
}
}
单例模式分为两类
饿汉式(Eager Initialization)
import lombok.Getter;
/**
* 饿汉式(Eager Initialization)
* 在类加载时就创建实例
* 线程安全,但可能造成资源浪费
*/
public class Singleton {
@Getter
private static Singleton instance = new Singleton();
private Singleton() {
}
}
懒汉式(Lazy Initialization)
非线程安全
/**
* 懒汉式(Lazy Initialization)
* 在第一次使用时才创建实例
* 节省资源,但需要注意线程安全问题
*/
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
线程安全版本(同步方法)
/**
* 懒汉式(Lazy Initialization)
*/
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
双重检查锁定
/**
* 懒汉式(Lazy Initialization)
* 在第一次使用时才创建实例
* <p>
* 既保证线程安全,又提高性能
* 只在实例为空时才进行同步检查
*/
public class Singleton {
// 1. 私有静态变量,用volatile修饰:禁止指令重排序,确保实例可见性
private static volatile Singleton instance;
// 2. 私有构造器:禁止外部直接创建实例
private Singleton() {
}
// 3. 公有静态获取实例方法:双重检查锁定
public static Singleton getInstance() {
// 第一次检查:未加锁,快速判断实例是否已存在(避免频繁进入同步块)
if (instance == null) {
// 加锁:确保同一时间只有一个线程进入实例创建逻辑
synchronized (Singleton.class) {
// 第二次检查:在锁内再次判断(防止多个线程等待锁时,重复创建实例)
if (instance == null) {
// 创建实例(volatile确保此操作不会被指令重排序)
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类
/**
* 懒汉式(Lazy Initialization)
* 在第一次使用时才创建实例
* <p>
* 利用类加载机制保证线程安全
* 实现延迟加载,性能优秀
*/
public class Singleton {
// 私有构造方法,防止外部实例化
private Singleton() {
// 可以添加防止反射破坏单例的逻辑
if (SingletonHolder.INSTANCE != null) {
throw new IllegalStateException("单例实例已存在,不能重复创建");
}
}
// 静态内部类,用于持有单例实例
private static class SingletonHolder {
// 静态常量,在类加载时初始化
private static final Singleton INSTANCE = new Singleton();
}
// 提供公共访问方法
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举实现
/**
* 懒汉式(Lazy Initialization)
* 在第一次使用时才创建实例
* <p>
* 最安全的实现方式
* 天然防止反射和序列化攻击
*/
public enum Singleton {
// 枚举实例,这就是单例的唯一实例
INSTANCE;
// 可以在这里添加单例类的属性和方法
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
// 其他业务方法
public void doSomething() {
System.out.println("单例对象执行操作");
}
}