重读 Java 设计模式: 解析单例模式,保证唯一实例的创建与应用

本周工作太忙了,变成了加班狗,下班回来也没时间写,只能利用周末时间写了😭。

好了,言归正传,本次我们先来介绍下设计模式中创建型模式-单例模式

一、引言

单例模式是设计模式中最简单但又最常用的一种模式之一。它确保某个类只能有一个实例,并提供了全局访问点,使得该实例在整个应用程序中都可以被访问。在本文中,我们将深入探讨单例模式的实现方式、应用场景以及实践指南。

二、基本概念

除了引言 一段中,单例模式除了实例全局唯一全局访问点外,还有其他特点,下面我将一一罗列出来:

  • 实例全局唯一:单例模式确保一个类只有一个实例对象存在,无论在何处访问该类,都将得到相同的实例。通过单例模式,可以防止不必要的多次实例化,确保系统中某个类只有一个实例存在。
  • 全局访问点:单例模式提供了一个全局的访问点,任何地方都可以通过该访问点获取到单例实例。
  • 延迟加载:许多单例模式的实现方式采用了延迟加载的策略,即在需要时才创建实例对象,而不是在类加载时就创建。
  • 线程安全:好的单例模式实现应该是线程安全的,即在多线程环境下也能够正确地保持单例对象的唯一性。
  • 节约资源:单例模式可以节约系统资源,特别是那些需要频繁创建和销毁的对象,如数据库连接、线程池等。
  • 全局状态管理:单例模式可以用于管理全局的状态信息,例如配置管理器、日志系统等

三、单例模式的五种经典实现案例

3.1 饿汉式单例模式

优点:

  • 线程安全:在类加载时就创建实例对象,因此不存在多线程环境下的线程安全问题。
  • 简单易理解:实现简单,易于理解和使用。

缺点:

  • 不支持延迟加载:在类加载时就创建实例对象,可能会导致资源浪费,尤其是在实例对象较大或者初始化过程较为复杂的情况下。
java 复制代码
package com.markus.desgin.mode.creational.singleton;

/**
 * @Author: zhangchenglong06
 * @Date: 2024/3/6
 * @Description: 单例模式一:急切初始化
 */
public class EagerlyInitSingleton {
  // 利用 类加载机制中 static 属性加载特性:全局只加载一遍,不会随实例的初始化重复加载
  private static EagerlyInitSingleton INSTANCE = new EagerlyInitSingleton();

  public static EagerlyInitSingleton getInstance() {
    return INSTANCE;
  }

  public static void main(String[] args) {
    EagerlyInitSingleton eagerlyInitSingleton1 = getInstance();
    EagerlyInitSingleton eagerlyInitSingleton2 = getInstance();

    System.out.println(eagerlyInitSingleton1 == eagerlyInitSingleton2);

  }
}

3.2 懒汉式单例模式

优点:

  • 延迟加载:只有在需要时才创建实例对象,节省了系统资源。
  • 简单易理解:实现简单,易于理解和使用。

缺点:

  • 线程不安全:在多线程环境下,如果多个线程同时调用获取实例的方法,可能会创建多个实例对象,导致单例模式失效。
  • 性能低下:由于使用了 synchronized 关键字进行同步,导致性能较低。
java 复制代码
package com.markus.desgin.mode.creational.singleton;

/**
 * @Author: zhangchenglong06
 * @Date: 2024/3/6
 * @Description: 单例模式二: 懒加载模式
 */
public class LazyInitSingleton {
  private static LazyInitSingleton INSTANCE = null;

  public static synchronized LazyInitSingleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new LazyInitSingleton();
    }
    return INSTANCE;
  }

  public static void main(String[] args) {
    LazyInitSingleton singleton1 = getInstance();
    LazyInitSingleton singleton2 = getInstance();

    System.out.println(singleton1 == singleton2);

  }
}

3.3 双重检验锁单例模式

优点:

  • 线程安全:通过双重检验锁机制来确保只有一个实例对象被创建,解决了懒汉式单例模式的线程安全问题。
  • 延迟加载:只有在需要时才创建实例对象,节省了系统资源。

缺点:

  • 实现复杂:双重检验锁的实现较为复杂,需要注意双重检验锁中的原子性问题。
java 复制代码
package com.markus.desgin.mode.creational.singleton;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * @Author: zhangchenglong06
 * @Date: 2024/3/6
 * @Description:
 */
public class DoubleCheckLockSingleton {
  // volatile 加入内存屏障机制,将 实例对象的初始化过程 顺序保证原子性
  private static volatile DoubleCheckLockSingleton INSTANCE;

  public static DoubleCheckLockSingleton getInstance() {
    if (INSTANCE == null) {
      // 局部加锁,相比懒汉式,性能更优
      synchronized (DoubleCheckLockSingleton.class) {
        if (INSTANCE == null) {
          // 这个阶段可能会由于 JVM 的性能优化,内部字节码出现乱序执行的情况,所以加上 volatile 关键字
          INSTANCE = new DoubleCheckLockSingleton();
        }
      }
    }
    return INSTANCE;
  }

  public static void main(String[] args) throws ExecutionException, InterruptedException {
    DoubleCheckLockSingleton singleton1 = getInstance();
    DoubleCheckLockSingleton singleton2 = getInstance();

    System.out.println(singleton1 == singleton2);

    CompletableFuture<DoubleCheckLockSingleton> future1 = CompletableFuture.supplyAsync(DoubleCheckLockSingleton::getInstance);
    CompletableFuture<DoubleCheckLockSingleton> future2 = CompletableFuture.supplyAsync(DoubleCheckLockSingleton::getInstance);
    singleton1 = future1.get();
    singleton2 = future2.get();
    System.out.println(singleton1 == singleton2);
  }
}

3.4 静态内部类单例模式

优点:

  • 线程安全:利用类加载机制保证了静态内部类只会被加载一次,从而保证了单例的线程安全性。
  • 延迟加载:只有在需要时才会加载静态内部类,从而实现了延迟加载。

缺点:

  • 不支持传参:静态内部类方式不能支持传递参数给单例的构造函数,因为静态内部类的实例化是由 JVM 保证线程安全的,不能传递参数。
java 复制代码
package com.markus.desgin.mode.creational.singleton;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * @Author: zhangchenglong06
 * @Date: 2024/3/6
 * @Description: 单例模式四: 静态内部类
 */
public class StaticInnerClassSingleton {
  private static class Singleton {
    public static StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
  }

  private StaticInnerClassSingleton() {
  }

  public static StaticInnerClassSingleton getInstance() {
    return Singleton.INSTANCE;
  }

  public static void main(String[] args) throws ExecutionException, InterruptedException {
    StaticInnerClassSingleton singleton1 = getInstance();
    StaticInnerClassSingleton singleton2 = getInstance();

    System.out.println(singleton1 == singleton2);

    CompletableFuture<StaticInnerClassSingleton> future1 = CompletableFuture.supplyAsync(StaticInnerClassSingleton::getInstance);
    CompletableFuture<StaticInnerClassSingleton> future2 = CompletableFuture.supplyAsync(StaticInnerClassSingleton::getInstance);
    singleton1 = future1.get();
    singleton2 = future2.get();
    System.out.println(singleton1 == singleton2);
  }
}

4.4 枚举单例模式

优点:

  • 线程安全:枚举类型是线程安全的,并且只会装载一次,从而保证了单例的线程安全性。
  • 防止反射攻击:枚举类型在反序列化时会检查是否为枚举,从而防止了反射攻击。

缺点:

  • 不支持延迟加载:枚举类型是在类加载时就实例化的,无法实现延迟加载,可能会导致资源浪
java 复制代码
package com.markus.desgin.mode.creational.singleton;

/**
 * @Author: zhangchenglong06
 * @Date: 2024/3/6
 * @Description: 单例模式五: 枚举
 */
public enum EnumSingleton {
  INSTANCE;

  public EnumSingleton getInstance(){
    return INSTANCE;
  }
}

单例模式在 Spring 中的应用场景

在 Spring 框架中,所有被 Spring 管理的 Bean 默认都是单例的。Spring 容器负责创建这些 Bean 的唯一实例,并在需要时注入到其他 Bean 中。

例如:

  • 业务 Spring Bean:这种一般是用户自定义的实例,将其定义到 Spring 框架中,并由 Spring 容器进行管理
  • 框架基础设施 Bean:
    • 资源加载器(ResourceLoader):Spring 框架提供了资源加载器来加载各种资源,如配置文件、静态文件等。资源加载器通常也是单例的,在整个应用程序中只存在一个实例,负责加载和管理资源。
    • 事件广播器(ApplicationEventPublisher):Spring 框架提供了事件机制来支持应用程序中的事件处理,而事件发布器用于发布事件通知给感兴趣的监听器。事件发布器通常也是单例的,在整个应用程序中只存在一个实例,负责发布事件通知。
    • 数据源(DataSource):在 Spring 中,数据源用于连接数据库,并提供了对数据库的访问。通常情况下,数据源也是单例的,在整个应用程序中只存在一个实例,负责管理数据库连接。
    • 事务管理器(PlatformTransactionManager):Spring 框架提供了事务管理器来管理事务的提交和回滚。事务管理器通常也是单例的,在整个应用程序中只存在一个实例,负责管理事务的生命周期。
    • 请求分发(DispatcherServlet):DispatcherServlet是Spring MVC框架中的前端控制器,负责接收HTTP请求并将请求分发给相应的控制器进行处理。在Spring MVC中,通常会配置一个全局的DispatcherServlet实例,以确保整个应用程序中只有一个DispatcherServlet实例。
    • 等等...

还有一些其他使用场景,例如 DefaultBeanNameGenerator、SimpleAutowireCandidateResolver 等等。

实践指南

在实践中,我们应该注意以下几点:

  • 线程安全性:在多线程环境下,要确保单例模式的实现是线程安全的,可以考虑使用双重检查锁或者静态内部类的方式来实现单例。
  • 懒加载与饿加载:根据项目的实际需求,选择合适的单例实现方式,是懒加载还是饿加载。
  • 防止反射和序列化攻击:考虑通过私有构造函数、枚举类型等方式来防止反射和序列化攻击。

本文总结

好了,总结一下:

本文详细介绍了单例模式在设计模式中的重要性以及在Spring框架中的应用场景。首先,我们从单例模式的基本概念出发,介绍了其特点和优势,包括全局唯一实例、延迟加载、线程安全等。然后,我们深入讨论了单例模式的五种经典实现案例,包括饿汉式、懒汉式、双重检验锁、静态内部类和枚举方式,每种实现方式的优缺点都进行了详细分析和比较。接着,我们探讨了单例模式在Spring框架中的应用场景,涵盖了Bean管理、IoC容器、AOP代理等各个方面。最后,我们给出了一些实践指南,帮助读者在实际项目中正确使用单例模式。

综上所述,单例模式作为一种简单但又非常重要的设计模式,在实际开发中有着广泛的应用,特别是在Spring框架等大型应用程序中。通过深入理解和正确应用单例模式,可以有效提高系统的性能和可维护性,是每个Java开发者都应该掌握的重要知识点。

相关推荐
等一场春雨4 分钟前
Java设计模式 八 适配器模式 (Adapter Pattern)
java·设计模式·适配器模式
一弓虽26 分钟前
java基础学习——jdbc基础知识详细介绍
java·学习·jdbc·连接池
王磊鑫26 分钟前
Java入门笔记(1)
java·开发语言·笔记
马剑威(威哥爱编程)1 小时前
2025春招 SpringCloud 面试题汇总
后端·spring·spring cloud
硬件人某某某1 小时前
Java基于SSM框架的社区团购系统小程序设计与实现(附源码,文档,部署)
java·开发语言·社区团购小程序·团购小程序·java社区团购小程序
程序员徐师兄1 小时前
Java 基于 SpringBoot 的校园外卖点餐平台微信小程序(附源码,部署,文档)
java·spring boot·微信小程序·校园外卖点餐·外卖点餐小程序·校园外卖点餐小程序
chengpei1471 小时前
chrome游览器JSON Formatter插件无效问题排查,FastJsonHttpMessageConverter导致Content-Type返回不正确
java·前端·chrome·spring boot·json
五味香1 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
Joeysoda1 小时前
Java数据结构 (从0构建链表(LinkedList))
java·linux·开发语言·数据结构·windows·链表·1024程序员节
扫地僧0091 小时前
(Java版本)基于JAVA的网络通讯系统设计与实现-毕业设计
java·开发语言