首页 > 基础资料 博客日记
设计模式-单例模式
2025-06-21 19:30:02基础资料围观11次
文章设计模式-单例模式分享给大家,欢迎收藏Java资料网,专注分享技术知识
什么是单例模式?
单例模式(Singleton Pattern)是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式又分懒汉模式和饿汉模式,两种都属于单例模式,只不过在实例化的时机不一样。单例模式有几个特点
1. 全局唯一:在系统中只能存在一个实例
2. 自行实例化:类内部负责创建实例,外部无法通过构造函数创建
3. 全局访问:提供一个获取出口获取实例,如静态方法或者属性获取
单例模式的优缺点
- 优点
- 节约资源,避免重复创建对象,减少内存占用
- 全局访问,方便系统内部共享数据
- 严格控制访问,确保对实例的访问是线程安全的
- 缺点
- 违反了单一原则,类既要负责功能,又要负责管理实例的生命周期
- 扩展性差,难以通过继承扩展功能
- 隐藏依赖关系
什么场景下使用单例模式
- 资源共享:如spring中的ApplicationContext就是一个单例模式,全局共享Bean对象。数据库链接池,项目中创建的线程池等
- 需要唯一协调者:如日志记录,任务调度器等
代码举例
这里会简单举例饿汉模式和懒汉模式的代码,和在项目中怎么应用
饿汉模式:
饿汉模式顾名思义,实例化很着急,在主类加载时就实例化了,这个时机非常早,相当于程序启动就实例化这个对象。下边就是代码举例
public class Singleton {
// 类加载时初始化实例
private static final Singleton INSTANCE = new Singleton();
// 私有构造器,防止外部实例化
private EagerSingleton() {}
// 公共访问点
public static Singleton getInstance() {
return INSTANCE;
}
}
懒汉模式:
懒汉模式看名称就知道,很懒惰不着急,什么时候用到才实例化,在程序告诉主类需要实例化的时候才会实例。相比较饿汉模式,实例化的时间要晚很多。懒汉模式在实例的时候不是线程安全,在实例化的时候得考虑一下线程安全的问题,这里会用到双重检查锁机制。下边是代码举例
public class Singleton {
// 使用volatile关键字防止指令重排
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;
}
}
如果你在封装一个底层的jar包,你的所管理的单例类一旦被修改就有错误,外部程序也可以通过反射、反序列化可以修改你的类,这个时候就得稍微修改一些代码了,一下以饿汉模式为例
-
防止反序列化
public class Singleton implements Serializable { private static final long serialVersionUID = 1L; private static final Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } // 防止反序列化创建新实例 public Object readResolve() { return INSTANCE; } }
-
防止反射
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() { // 防止反射 if (INSTANCE != null) { throw new IllegalStateException("Already initialized."); } } public static Singleton getInstance() { return INSTANCE; } }
那么怎么在生产使用呢?常见的场景有缓存,线程池。大多数在生产当中绝大多数单例模式的类都是交给spring控制生命周期的,spring模式的就是单例模式。如果不想也可以自己手写实现,就以缓存为例
@Slf4j
public class GlobalCache implements Serializable {
// 单例模式
private static volatile GlobalCache INSTANCE;
// 本地缓存(Caffeine)
private final Cache<String, Object> localCache;
// 远程缓存(Redis)
private final RedisTemplate<String, Object> redisTemplate;
// 私有构造器
private GlobalCache() {
if (INSTANCE != null) {
throw new IllegalStateException("Already initialized.");
}
// 初始化本地缓存
this.localCache = Caffeine.newBuilder()
.maximumSize(1000) // 最大缓存数量
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
.build();
// 初始化RedisTemplate(实际项目中通过Spring注入)
this.redisTemplate = createRedisTemplate();
}
// 公共访问点
public static GlobalCache getInstance() {
if (INSTANCE == null) { // 第一次检查,不加锁
synchronized (GlobalCache.class) {
if (INSTANCE == null) { // 第二次检查,加锁后
INSTANCE = new GlobalCache();
}
}
}
return INSTANCE;
}
public Object readResolve() {
return INSTANCE;
}
// 创建RedisTemplate实例(
private RedisTemplate<String, Object> createRedisTemplate() {
// 这里通过spring的上下文可以获取
}
/**
* 从缓存获取数据
*/
public Object get(String key) {
// 1. 先查本地缓存
Object value = localCache.getIfPresent(key);
if (value != null) {
return value;
}
// 2. 再查Redis缓存
value = redisTemplate.opsForValue().get(key);
if (value != null) {
// 更新本地缓存
localCache.put(key, value);
return value;
}
log.info("缓存未命中: {}", key);
return null;
}
/**
* 放入缓存
*/
public void put(String key, Object value, long redisExpireTime, TimeUnit timeUnit) {
// 放入本地缓存
localCache.put(key, value);
// 放入Redis缓存
redisTemplate.opsForValue().set(key, value, redisExpireTime, timeUnit);
}
/**
* 失效缓存
*/
public void invalidate(String key) {
// 先失效本地缓存
localCache.invalidate(key);
// 再失效Redis缓存
redisTemplate.delete(key);
log.info("缓存已失效: {}", key);
}
}
文章来源:https://www.cnblogs.com/MaC-Matthew/p/18940555
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:
上一篇:hot100之堆
下一篇:没有了