各种单例模式的实现方式

前言

之前在这篇 一文漫谈设计模式之创建型模式(一) 文章留了个坑位,现在来说完成下。

实现方式 线程安全 延迟加载 关键特点/原理 推荐指数
饿汉式 ✅ 安全 ❌ 不支持 类加载时即初始化实例。实现简单,但可能造成资源浪费。 ⭐⭐⭐
懒汉式(基础版) ❌ 不安全 ✅ 支持 首次调用时创建实例。实现简单,但多线程下不安全。 ⭐
懒汉式(同步方法) ✅ 安全 ✅ 支持 在getInstance方法上加synchronized锁。线程安全但效率低。 ⭐⭐
双重校验锁(DCL) ✅ 安全 ✅ 支持 两次检查实例是否为null,配合volatile关键字避免指令重排序。 ⭐⭐⭐⭐
静态内部类 ✅ 安全 ✅ 支持 利用类加载机制保证线程安全,由JVM在首次使用时加载内部类并初始化实例。 ⭐⭐⭐⭐⭐
枚举 ✅ 安全 ❌ 不支持(本质饿汉式) 写法简洁,由JVM保证实例唯一性,能防止反射和反序列化破坏单例。 ⭐⭐⭐⭐⭐

案例

饿汉式 (Eager Initialization)

java 复制代码
public class EagerSingleton {
    // 类加载时就初始化实例
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    
    // 私有构造器防止外部实例化
    private EagerSingleton() {
        System.out.println("EagerSingleton instance created!");
    }
    
    // 全局访问点
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
    
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

懒汉式 - 基础版 (非线程安全)

java 复制代码
public class LazySingleton {
    // 初始化为null,需要时才创建
    private static LazySingleton instance;
    
    private LazySingleton() {
        System.out.println("LazySingleton instance created!");
    }
    
    // 非线程安全的懒加载
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
    
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

懒汉式 - 同步方法版

java 复制代码
public class SynchronizedLazySingleton {
    private static SynchronizedLazySingleton instance;
    
    private SynchronizedLazySingleton() {
        System.out.println("SynchronizedLazySingleton instance created!");
    }
    
    // 在方法级别加synchronized保证线程安全
    public static synchronized SynchronizedLazySingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedLazySingleton();
        }
        return instance;
    }
    
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

双重校验锁 (DCL - Double Checked Locking)

java 复制代码
public class DCLSingleton {
    // volatile防止指令重排序
    private static volatile DCLSingleton instance;
    
    private DCLSingleton() {
        System.out.println("DCLSingleton instance created!");
    }
    
    public static DCLSingleton getInstance() {
        if (instance == null) {  // 第一次检查,避免不必要的同步
            synchronized (DCLSingleton.class) {  // 同步块
                if (instance == null) {  // 第二次检查
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
    
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

关键点解析

(1) volatile关键字的作用

禁止指令重排序:instance = new Singleton()在JVM中分为三步:1分配内存空间;2初始化对象;3将实例指向内存地址。

若未使用volatile,JVM可能按1->3->2的顺序执行(指令重排序)。此时若线程A执行到步骤3(未初始化),线程B在第一次检查时可能直接返回未初始化的实例,导致错误。

保证可见性:确保多线程环境下,一个线程对instance的修改能立即被其他线程看到。

(2) 双重检查的必要性:

第一次检查:避免每次调用getInstance都进入同步块,提升性能。

第二次检查:防止多个线程同时通过第一次检查后,重复创建实例。例如,线程A和B均通过第一次检查,A先获取锁并创建实例后,B再次获取锁时需第二次检查以避免重复创建。

静态内部类 (Static Inner Class / Holder)

java 复制代码
public class InnerClassSingleton {
    
    private InnerClassSingleton() {
        System.out.println("InnerClassSingleton instance created!");
    }
    
    // 静态内部类,只有在被引用时才会加载
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;  // 这里才会触发内部类的加载
    }
    
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

枚举

java 复制代码
public enum EnumSingleton {
    INSTANCE;  // 枚举实例,由JVM保证唯一性
    
    // 可以添加任意方法
    public void doSomething() {
        System.out.println("EnumSingleton doing something...");
    }
    
    // 也可以有属性
    private String data;
    
    public String getData() {
        return data;
    }
    
    public void setData(String data) {
        this.data = data;
    }
}

// 使用方式:
// EnumSingleton singleton = EnumSingleton.INSTANCE;
// singleton.doSomething();

测试代码

java 复制代码
public class SingletonTest {
    public static void main(String[] args) {
        // 测试饿汉式
        EagerSingleton eager1 = EagerSingleton.getInstance();
        EagerSingleton eager2 = EagerSingleton.getInstance();
        System.out.println("EagerSingleton same instance? " + (eager1 == eager2));
        
        // 测试静态内部类
        InnerClassSingleton inner1 = InnerClassSingleton.getInstance();
        InnerClassSingleton inner2 = InnerClassSingleton.getInstance();
        System.out.println("InnerClassSingleton same instance? " + (inner1 == inner2));
        
        // 测试枚举
        EnumSingleton enum1 = EnumSingleton.INSTANCE;
        EnumSingleton enum2 = EnumSingleton.INSTANCE;
        System.out.println("EnumSingleton same instance? " + (enum1 == enum2));
        
        // 多线程测试
        testWithMultipleThreads();
    }
    
    private static void testWithMultipleThreads() {
        System.out.println("\nTesting with 10 threads:");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazySingleton instance = LazySingleton.getInstance();
                System.out.println("Thread " + Thread.currentThread().getId() + 
                                 " got instance: " + instance.hashCode());
            }).start();
        }
    }
}

总结

关键要点说明:

饿汉式:最简单的实现,但可能在未使用时占用内存

基础懒汉式:在多线程环境下可能会创建多个实例

同步方法懒汉式:线程安全但性能差(每次调用都要同步)

双重校验锁:需要volatile关键字,适用于JDK 5+

静态内部类:优雅的延迟加载实现,推荐使用

枚举:最安全的实现,天然防止反射和序列化破坏

在实际开发中,如果不需要延迟加载,推荐使用枚举;如果需要延迟加载,推荐使用静态内部类。这两种方式都是《Effective Java》推荐的单例实现方式。

相关推荐
别动我齐刘海1 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
code2cat3 小时前
Java进阶篇之AtomicLongFieldUpdater:把累计额度留在对象字段中
java·cas·并发编程
WeiXin_DZbishe3 小时前
基于springboot城市公交实时运营与调度系统-计算机毕设 附源码【课程设计】66173
java·vue.js·spring boot·后端·python·php·课程设计
泡茶喝茶写代码3 小时前
A股量化数据工程:从 REST 接口到策略信号(第 18 篇):放量缩量异动扫描
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
wewowilling3 小时前
认识微服务
java·运维·微服务·springcloud
代码什么用3 小时前
Java Web 7-10
java·servlet
蜗牛互联网5 小时前
GPT-6 与 Intelligent UI:从回答到可审计操作的产品迁移
java·人工智能·后端
SL_staff6 小时前
运维救火实录:钉钉考勤API变更引发工资计算异常,我们用JVS-Logic可视化逻辑引擎实现分钟级热修复
java·mysql·全栈
SunnyDays10116 小时前
Java 查找并高亮 Word 文字:精确查找及正则表达式模糊查找
java·查找文字·word 文档·高亮文字·正则匹配查找·模糊查找