蔫吧枣干巴梨 發表於 2025-6-21 19:12:00

设计模式-单例模式

<h3 id="什么是单例模式">什么是单例模式?</h3>
<p>单例模式(Singleton Pattern)<strong>是一种创建型设计模式</strong>,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式又分懒汉模式和饿汉模式,两种都属于单例模式,只不过在实例化的时机不一样。单例模式有几个特点</p>
<p><strong>1. 全局唯一</strong>:在系统中只能存在一个实例<br>
<strong>2. 自行实例化</strong>:类内部负责创建实例,外部无法通过构造函数创建<br>
<strong>3. 全局访问</strong>:提供一个获取出口获取实例,如静态方法或者属性获取</p>
<h3 id="单例模式的优缺点">单例模式的优缺点</h3>
<ul>
<li>优点
<ol>
<li>节约资源,避免重复创建对象,减少内存占用</li>
<li>全局访问,方便系统内部共享数据</li>
<li>严格控制访问,确保对实例的访问是线程安全的</li>
</ol>
</li>
<li>缺点
<ol>
<li>违反了单一原则,类既要负责功能,又要负责管理实例的生命周期</li>
<li>扩展性差,难以通过继承扩展功能</li>
<li>隐藏依赖关系</li>
</ol>
</li>
</ul>
<h3 id="什么场景下使用单例模式">什么场景下使用单例模式</h3>
<ol>
<li>资源共享:如spring中的ApplicationContext就是一个单例模式,全局共享Bean对象。数据库链接池,项目中创建的线程池等</li>
<li>需要唯一协调者:如日志记录,任务调度器等</li>
</ol>
<h3 id="代码举例">代码举例</h3>
<p>这里会简单举例饿汉模式和懒汉模式的代码,和在项目中怎么应用</p>
<p><strong>饿汉模式:</strong></p>
<p>饿汉模式顾名思义,实例化很着急,在主类加载时就实例化了,这个时机非常早,相当于程序启动就实例化这个对象。下边就是代码举例</p>
<pre><code class="language-java">public class Singleton {
    // 类加载时初始化实例
    private static final Singleton INSTANCE = new Singleton();
   
    // 私有构造器,防止外部实例化
    private EagerSingleton() {}
   
    // 公共访问点
    public static Singleton getInstance() {
      return INSTANCE;
    }
}
</code></pre>
<p><strong>懒汉模式:</strong></p>
<p>懒汉模式看名称就知道,很懒惰不着急,什么时候用到才实例化,在程序告诉主类需要实例化的时候才会实例。相比较饿汉模式,实例化的时间要晚很多。懒汉模式在实例的时候不是线程安全,在实例化的时候得考虑一下线程安全的问题,这里会用到双重检查锁机制。下边是代码举例</p>
<pre><code class="language-java">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;
    }
}
</code></pre>
<p>如果你在封装一个底层的jar包,你的所管理的单例类一旦被修改就有错误,外部程序也可以通过反射、反序列化可以修改你的类,这个时候就得稍微修改一些代码了,一下以饿汉模式为例</p>
<ol>
<li>
<p>防止反序列化</p>
<pre><code class="language-java">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;
    }
}
</code></pre>
</li>
<li>
<p>防止反射</p>
<pre><code class="language-java">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;
    }
}
</code></pre>
</li>
</ol>
<p>那么怎么在生产使用呢?常见的场景有缓存,线程池。大多数在生产当中绝大多数单例模式的类都是交给spring控制生命周期的,spring模式的就是单例模式。如果不想也可以自己手写实现,就以缓存为例</p>
<pre><code class="language-java">@Slf4j
public class GlobalCache implements Serializable {
    // 单例模式
    private static volatile GlobalCache INSTANCE;
    // 本地缓存(Caffeine)
    private final Cache&lt;String, Object&gt; localCache;
    // 远程缓存(Redis)
    private final RedisTemplate&lt;String, Object&gt; 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&lt;String, Object&gt; 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);
    }
}
</code></pre><br><br>
来源:https://www.cnblogs.com/MaC-Matthew/p/18940555
頁: [1]
查看完整版本: 设计模式-单例模式