找回密码
 立即注册
首页 业界区 业界 设计模式-单例模式

设计模式-单例模式

孙淼淼 2025-6-26 10:50:08
什么是单例模式?

单例模式(Singleton Pattern)是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式又分懒汉模式和饿汉模式,两种都属于单例模式,只不过在实例化的时机不一样。单例模式有几个特点
1. 全局唯一:在系统中只能存在一个实例
2. 自行实例化:类内部负责创建实例,外部无法通过构造函数创建
3. 全局访问:提供一个获取出口获取实例,如静态方法或者属性获取
单例模式的优缺点


  • 优点

    • 节约资源,避免重复创建对象,减少内存占用
    • 全局访问,方便系统内部共享数据
    • 严格控制访问,确保对实例的访问是线程安全的

  • 缺点

    • 违反了单一原则,类既要负责功能,又要负责管理实例的生命周期
    • 扩展性差,难以通过继承扩展功能
    • 隐藏依赖关系

什么场景下使用单例模式


  • 资源共享:如spring中的ApplicationContext就是一个单例模式,全局共享Bean对象。数据库链接池,项目中创建的线程池等
  • 需要唯一协调者:如日志记录,任务调度器等
代码举例

这里会简单举例饿汉模式和懒汉模式的代码,和在项目中怎么应用
饿汉模式:
饿汉模式顾名思义,实例化很着急,在主类加载时就实例化了,这个时机非常早,相当于程序启动就实例化这个对象。下边就是代码举例
  1. public class Singleton {
  2.     // 类加载时初始化实例
  3.     private static final Singleton INSTANCE = new Singleton();
  4.    
  5.     // 私有构造器,防止外部实例化
  6.     private EagerSingleton() {}
  7.    
  8.     // 公共访问点
  9.     public static Singleton getInstance() {
  10.         return INSTANCE;
  11.     }
  12. }
复制代码
懒汉模式:
懒汉模式看名称就知道,很懒惰不着急,什么时候用到才实例化,在程序告诉主类需要实例化的时候才会实例。相比较饿汉模式,实例化的时间要晚很多。懒汉模式在实例的时候不是线程安全,在实例化的时候得考虑一下线程安全的问题,这里会用到双重检查锁机制。下边是代码举例
  1. public class Singleton {
  2.     // 使用volatile关键字防止指令重排
  3.     private static volatile Singleton instance;
  4.    
  5.     private Singleton() {}
  6.    
  7.     public static Singleton getInstance() {
  8.         if (instance == null) {  // 第一次检查,不加锁
  9.             synchronized (Singleton.class) {
  10.                 if (instance == null) {  // 第二次检查,加锁后
  11.                     instance = new Singleton();
  12.                 }
  13.             }
  14.         }
  15.         return instance;
  16.     }
  17. }
复制代码
如果你在封装一个底层的jar包,你的所管理的单例类一旦被修改就有错误,外部程序也可以通过反射、反序列化可以修改你的类,这个时候就得稍微修改一些代码了,一下以饿汉模式为例

  • 防止反序列化
    1. public class Singleton implements Serializable {
    2.     private static final long serialVersionUID = 1L;
    3.     private static final Singleton INSTANCE = new Singleton();
    4.    
    5.     private Singleton() {}
    6.    
    7.     public static Singleton getInstance() {
    8.         return INSTANCE;
    9.     }
    10.    
    11.     // 防止反序列化创建新实例
    12.     public Object readResolve() {
    13.         return INSTANCE;
    14.     }
    15. }
    复制代码
  • 防止反射
    1. public class Singleton {
    2.     private static final Singleton INSTANCE = new Singleton();
    3.    
    4.     private Singleton() {
    5.         // 防止反射
    6.         if (INSTANCE != null) {
    7.             throw new IllegalStateException("Already initialized.");
    8.         }
    9.     }
    10.    
    11.     public static Singleton getInstance() {
    12.         return INSTANCE;
    13.     }
    14. }
    复制代码
那么怎么在生产使用呢?常见的场景有缓存,线程池。大多数在生产当中绝大多数单例模式的类都是交给spring控制生命周期的,spring模式的就是单例模式。如果不想也可以自己手写实现,就以缓存为例
  1. @Slf4j
  2. public class GlobalCache implements Serializable {
  3.     // 单例模式
  4.     private static volatile GlobalCache INSTANCE;
  5.     // 本地缓存(Caffeine)
  6.     private final Cache<String, Object> localCache;
  7.     // 远程缓存(Redis)
  8.     private final RedisTemplate<String, Object> redisTemplate;
  9.     // 私有构造器
  10.     private GlobalCache() {
  11.         if (INSTANCE != null) {
  12.             throw new IllegalStateException("Already initialized.");
  13.         }
  14.         // 初始化本地缓存
  15.         this.localCache = Caffeine.newBuilder()
  16.                 .maximumSize(1000)           // 最大缓存数量
  17.                 .expireAfterWrite(10, TimeUnit.MINUTES)  // 写入后10分钟过期
  18.                 .build();
  19.         // 初始化RedisTemplate(实际项目中通过Spring注入)
  20.         this.redisTemplate = createRedisTemplate();
  21.     }
  22.     // 公共访问点
  23.     public static GlobalCache getInstance() {
  24.         if (INSTANCE == null) {  // 第一次检查,不加锁
  25.             synchronized (GlobalCache.class) {
  26.                 if (INSTANCE == null) {  // 第二次检查,加锁后
  27.                     INSTANCE = new GlobalCache();
  28.                 }
  29.             }
  30.         }
  31.         return INSTANCE;
  32.     }
  33.     public Object readResolve() {
  34.         return INSTANCE;
  35.     }
  36.     // 创建RedisTemplate实例(
  37.     private RedisTemplate<String, Object> createRedisTemplate() {
  38.         // 这里通过spring的上下文可以获取
  39.     }
  40.     /**
  41.      * 从缓存获取数据
  42.      */
  43.     public Object get(String key) {
  44.         // 1. 先查本地缓存
  45.         Object value = localCache.getIfPresent(key);
  46.         if (value != null) {
  47.             return value;
  48.         }
  49.         // 2. 再查Redis缓存
  50.         value = redisTemplate.opsForValue().get(key);
  51.         if (value != null) {
  52.             // 更新本地缓存
  53.             localCache.put(key, value);
  54.             return value;
  55.         }
  56.         log.info("缓存未命中: {}", key);
  57.         return null;
  58.     }
  59.     /**
  60.      * 放入缓存
  61.      */
  62.     public void put(String key, Object value, long redisExpireTime, TimeUnit timeUnit) {
  63.         // 放入本地缓存
  64.         localCache.put(key, value);
  65.         // 放入Redis缓存
  66.         redisTemplate.opsForValue().set(key, value, redisExpireTime, timeUnit);
  67.     }
  68.     /**
  69.      * 失效缓存
  70.      */
  71.     public void invalidate(String key) {
  72.         // 先失效本地缓存
  73.         localCache.invalidate(key);
  74.         // 再失效Redis缓存
  75.         redisTemplate.delete(key);
  76.         log.info("缓存已失效: {}", key);
  77.     }
  78. }
复制代码
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册