各种单例模式的实现方式

前言

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

实现方式 线程安全 延迟加载 关键特点/原理 推荐指数
饿汉式 ✅ 安全 ❌ 不支持 类加载时即初始化实例。实现简单,但可能造成资源浪费。 ⭐⭐⭐
懒汉式(基础版) ❌ 不安全 ✅ 支持 首次调用时创建实例。实现简单,但多线程下不安全。
懒汉式(同步方法) ✅ 安全 ✅ 支持 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 小时前
【从零入门23种设计模式02】创建型之单例模式(5种实现形式)
java·spring boot·后端·单例模式·设计模式·职场和发展
极客先躯1 小时前
高级java每日一道面试题-2025年7月11日-基础篇[LangChain4j]-如何管理 LangChain4j 应用的配置?请描述配置的最佳实践。
java·langchain·团队协作·密钥管理·动态调整·敏感信息保护·多环境支持
逆境不可逃1 小时前
【从零入门23种设计模式04】创建型之原型模式
java·后端·算法·设计模式·职场和发展·开发·原型模式
追随者永远是胜利者10 小时前
(LeetCode-Hot100)53. 最大子数组和
java·算法·leetcode·职场和发展·go
百锦再11 小时前
Java之Volatile 关键字全方位解析:从底层原理到最佳实践
java·开发语言·spring boot·struts·kafka·tomcat·maven
张万森爱喝可乐11 小时前
Java高并发实战
java
daad77711 小时前
rcu 内核线程
java·开发语言
百锦再12 小时前
Java JUC并发编程全面解析:从原理到实战
java·开发语言·spring boot·struts·kafka·tomcat·maven