• 技术成神之路:设计模式(一)单例模式


    在软件设计中,有时我们希望某个类的实例始终是唯一的,即无论在何处访问这个类,都能够得到同一个实例。单例模式(Singleton Pattern)就是为了解决这个问题而产生的。单例模式确保一个类只有一个实例,并提供一个全局访问点。

    1.定义


    单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。其主要思想是将类的构造函数私有化,并通过一个静态方法来控制实例的创建和访问。

    2.常见实现方式


    单例模式有多种实现方式,下面介绍几种常见的实现方式:

    2.1饿汉式(Eager Initialization)

    饿汉式是在类加载时就创建实例,这样可以确保线程安全,并且在类首次使用前完成实例化。

    示例代码:

    public class Singleton {
        private static final Singleton INSTANCE = new Singleton();
    
        private Singleton() {
            // 私有构造函数,防止外部实例化
        }
    
        public static Singleton getInstance() {
            return INSTANCE;
        }
    }
    

    优点:简单,易于理解,线程安全
    缺点:类加载时即创建实例,可能造成资源浪费

    如果你一定会使用该类,这种方式无疑是最简单的方法

    2.2 懒汉式(Lazy Initialization)

    懒汉式是在第一次调用 getInstance() 方法时创建实例。这种方式避免了饿汉式的资源浪费问题。

    示例代码:

    public class Singleton {
        private static Singleton instance;
    
        private Singleton() {
            // 私有构造函数,防止外部实例化
        }
    
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    优点:实现简单,延迟实例化,避免资源浪费
    缺点:使用了 synchronized,在高并发情况下性能可能较差

    每次调用getInstance()都会进行同步检查,这样会消耗不必要的资源,不推荐使用

    2.3双重检查锁(Double-Checked Locking)

    双重检查锁在懒汉式的基础上,通过减少使用 synchronized 来提高性能。

    示例代码:

    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;  // 返回实例
        }
    }
    
    

    优点:延迟实例化,提高了性能
    缺点:实现复杂,容易出错

    同步代码块含义:因为可能会有多个线程同时通过了第一次检查,在进入同步块之后,再次检查可以确保只有一个线程创建实例,最大限度地在提升性能的条件下保证了线程安全。

    emm… 个人不喜欢这种笨重写法

    2.4 静态内部类(Static Inner Class)

    这种方式使用了类加载机制来确保线程安全,同时实现了延迟加载。

    示例代码:

    public class Singleton {
        private Singleton() {
            // 私有构造函数,防止外部实例化
        }
    
         // 静态内部类,利用类加载机制保证线程安全且延迟加载
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    优点:延迟实例化,线程安全,实现简单
    缺点:无法传递外部参数

    这时候就会有同学要问了,何为类加载机制,问的好,所谓类加载机制就算:JVM 在加载类的过程中,静态内部类 SingletonHolder 中的静态变量 INSTANCE 只会被实例化一次,由JVM保证其线程安全性,所以在多线程环境下可以安全地使用。

    2.5 枚举(Enum)

    这种方法是Effective Java作者Joshua Bloch推荐的单例实现方式之一,它解决了传统单例模式实现中的一些问题,比如序列化、反射攻击等。

    示例代码:

    public enum Singleton {
        INSTANCE;
    
        public void doSomething() {
            // 业务方法
        }
    }
    

    优点:简洁,线程安全,防止反序列化破坏单例
    缺点:无法灵活控制实例化过程

    使用方法:

    Singleton.INSTANCE.doSomething();
    

    特点和优势

    1. 线程安全性:
      枚举类型的实例创建是线程安全的,JVM在加载枚举类型时会通过类加载器保证只实例化一次。因此,多线程环境下也能保证单例的唯一性。

    2. 防止反射攻击:
      枚举类型的实例创建是由JVM控制的,因此无法通过反射来创建枚举类的实例。这样可以防止反射攻击,即使是在枚举类中添加了私有构造函数也不例外。

    3. 防止序列化问题:
      Java枚举类型在序列化和反序列化时会自动处理,确保在序列化和反序列化过程中都是单例的。

    4. 简洁且高效:
      枚举实现单例模式非常简洁,只需声明一个枚举类型即可,不需要额外的代码来保证线程安全和单例特性。

    3.单例模式的注意事项


    • 线程安全:确保在多线程环境下一个类只有一个实例。
    • 延迟加载:尽量避免在类加载时就实例化,除非明确知道实例一定会被使用。
    • 防止反射攻击:通过在构造函数中添加判断来防止反射创建多个实例。
    • 防止反序列化破坏单例:在实现 Serializable 接口时,提供 `readResolve 方法。

    4.总结


    五种创建单例的方式,大家按需选择,核心思想都是确保一个类只有一个实例,并提供全局访问点,没有最好的,只有最适合的,理解不同实现方式的优缺点,可以帮助我们在实际开发中选择最合适的方案。

  • 相关阅读:
    Linux高级IO
    Turtlebot4入门教程-快速开始
    大量需求测不过来怎么破?
    单调队列/单调栈优化dp
    2023最新CTF入门的正确姿势
    Authing 入选长城战略咨询《中国哪吒企业发展报告 2022》
    使用vue-cli搭建SPA项目
    Docker入门之安装Tomcat
    云存储架构——打造安全的企业级数据流转平台技术方案
    Linux教程:如何安装redis服务并搭建三主三从集群部署环境
  • 原文地址:https://blog.csdn.net/WriteBug001/article/details/140093770