各种单例模式的实现方式

前言

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

实现方式 线程安全 延迟加载 关键特点/原理 推荐指数
饿汉式 ✅ 安全 ❌ 不支持 类加载时即初始化实例。实现简单,但可能造成资源浪费。 ⭐⭐⭐
懒汉式(基础版) ❌ 不安全 ✅ 支持 首次调用时创建实例。实现简单,但多线程下不安全。
懒汉式(同步方法) ✅ 安全 ✅ 支持 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》推荐的单例实现方式。

相关推荐
莫逸风3 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
z123456789863 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin5211234 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python
做个文艺程序员4 小时前
Linux第24篇:Java应用监控体系搭建:Prometheus+Grafana可视化运维
java·grafana·prometheus
小钻风33665 小时前
Spring Boot 文件上传详解:深入理解 MultipartFile 的使用与原理
java·开发语言
其美杰布-富贵-李5 小时前
Spring Boot 工程开发全流程说明
java·spring boot·后端
张小姐的猫6 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
前端双越老师7 小时前
如何以前端视角(非0基础)学 Java ?
java·node.js·全栈
dear_bi_MyOnly7 小时前
【SpringBoot配置文件】
java·spring boot·后端·学习·spring·java-ee·学习方法
做个文艺程序员7 小时前
Linux第22篇:用Docker容器化你的Java SaaS应用:一次构建,随处运行
java·docker·容器