在设计模式中,单例模式(Singleton Pattern)被认为是最基础、最常用的一种创建型模式。尽管很多开发者声称早已熟练掌握,但真正理解其实现方式、适用场景以及各种实现方式之间的差异,并非易事。本文将通过实际例子与深度解析,系统性讲清楚什么是单例模式、为什么需要单例模式以及单例模式的六种主流实现方式。


一、什么是单例模式?

单例模式,顾名思义,就是确保在整个应用生命周期中,一个类只能有一个实例,并提供一个全局访问点。

它的核心目标是:

  • 控制实例数量(通常为1)
  • 节省系统资源
  • 避免资源冲突
  • 提供全局访问

二、为什么要用单例模式?

在实际开发中,以下两类场景最常见地需要使用单例模式:

1. 管理临界资源,避免资源冲突

举个例子:日志文件写入类。

public class Logger {
    public void log(String message) {
        // 向 log.txt 写入内容
    }
}

如果每次都通过 new Logger() 创建对象,在并发环境下,多个 Logger 实例可能同时向同一个文件写入日志,导致文件句柄冲突、数据错乱甚至抛出异常。

通过将 Logger 类设计为单例,可以确保只有一个实例操作该文件,从而有效避免并发写入冲突。

2. 保证全局唯一对象,协调统一管理

典型例子:数据库连接池、主键生成器等。

例如下面这个 ID 生成器类:

public class IdGenerator {
    private long counter = 0;

    public synchronized long getId() {
        return counter++;
    }
}

如果每次都创建新的 IdGenerator 实例,每个对象的 counter 初始值都是 0,生成的 ID 就会出现重复,破坏唯一性约束。因此,ID 生成器必须在整个系统中保持单例。


三、单例模式的六种实现方式

实现思路简述:

实现方式是否线程安全是否延迟加载复杂度适用场景
饿汉式是否简单对资源需求小或初始化成本低的对象
懒汉式否(需加锁)是中对性能要求低的小型系统
双重检查是是稍复杂性能优化场景
静态内部类是是较优雅推荐实践方式
枚举式是是最简单官方推荐,适合所有场景
容器式(扩展)视实现而定可支持通常较复杂大量单例管理需求

1. 饿汉式(Eager Initialization)

实现原理:类加载时就完成实例化,线程安全,但不支持延迟加载。

public class IdGenerator {
    private static final IdGenerator instance = new IdGenerator();

    private IdGenerator() {}

    public static IdGenerator getInstance() {
        return instance;
    }
}

优点:

  • 简单易懂
  • JVM 保证线程安全

缺点:

  • 即使未使用也会创建实例,浪费资源(尤其是重量级类)

2. 懒汉式(Lazy Initialization)

实现原理:在第一次调用时创建对象。非线程安全,需手动加锁。

public class IdGenerator {
    private static IdGenerator instance;

    private IdGenerator() {}

    public static IdGenerator getInstance() {
        if (instance == null) {
            instance = new IdGenerator();
        }
        return instance;
    }
}

问题:多线程环境下可能创建多个实例,必须加锁。

加锁后的线程安全版本:

public class IdGenerator {
    private static IdGenerator instance;

    private IdGenerator() {}

    public static synchronized IdGenerator getInstance() {
        if (instance == null) {
            instance = new IdGenerator();
        }
        return instance;
    }
}

缺点:

  • 首次调用性能差
  • 多线程性能瓶颈明显

3. 双重检查锁(Double-Checked Locking)

实现原理:先判断是否需要加锁,加锁后再次判断是否需要创建实例。

public class IdGenerator {
    private static volatile IdGenerator instance;

    private IdGenerator() {}

    public static IdGenerator getInstance() {
        if (instance == null) {
            synchronized (IdGenerator.class) {
                if (instance == null) {
                    instance = new IdGenerator();
                }
            }
        }
        return instance;
    }
}

关键点:

  • volatile 防止指令重排
  • 第一次判断提升性能
  • 第二次判断保证线程安全

适用场景:对性能有要求但又需要延迟加载的场景


4. 静态内部类(推荐方式)

实现原理:利用 Java 类加载机制延迟加载,且由 JVM 保证线程安全。

public class IdGenerator {
    private IdGenerator() {}

    private static class SingletonHolder {
        private static final IdGenerator INSTANCE = new IdGenerator();
    }

    public static IdGenerator getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

优点:

  • 延迟加载
  • 线程安全
  • 实现简单优雅

注意:类加载器会在第一次调用 getInstance() 时加载内部类,从而实现懒加载。


5. 枚举方式(官方推荐)

实现原理:利用 Java 枚举的天然单例特性。

public enum IdGenerator {
    INSTANCE;

    private long counter = 0;

    public synchronized long getId() {
        return counter++;
    }
}

使用方式:

long id = IdGenerator.INSTANCE.getId();

优点:

  • 写法最简单
  • JVM 从语言层面保证线程安全和单例性
  • 防止反射与反序列化攻击

缺点:

  • 写法略有语法限制,不适合对类结构有复杂依赖的情境

6. 容器式单例(扩展)

有些系统需要管理多个单例类,可使用容器统一管理。

public class SingletonManager {
    private static Map<String, Object> singletonMap = new HashMap<>();

    public static void registerService(String key, Object instance) {
        if (!singletonMap.containsKey(key)) {
            singletonMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return singletonMap.get(key);
    }
}

适合场景:框架或容器层封装多个单例服务


四、如何选择单例实现?

实现方式延迟加载线程安全推荐级别适用说明
饿汉式否是⭐⭐⭐类加载即实例化,适合轻量级单例
懒汉式是否(需加锁)⭐代码简单,但线程不安全
双重检查是是⭐⭐性能和安全兼顾,复杂度高
静态内部类是是⭐⭐⭐⭐推荐实现,优雅安全
枚举方式是是⭐⭐⭐⭐⭐最安全简洁,官方推荐
容器式可选视情况而定⭐⭐管理多个单例的场景

五、实际项目中如何落地?

示例:日志管理类

public enum Logger {
    INSTANCE;

    public void log(String message) {
        // 线程安全写入日志
    }
}

示例:主键生成器

public class IdGenerator {
    private long counter = 0;

    private IdGenerator() {}

    private static class Holder {
        private static final IdGenerator INSTANCE = new IdGenerator();
    }

    public static IdGenerator getInstance() {
        return Holder.INSTANCE;
    }

    public synchronized long getId() {
        return counter++;
    }
}

六、总结

  • 单例模式的本质是控制对象的唯一性和全局访问性。
  • 不同的实现方式适配不同的性能、线程安全、延迟加载等需求。
  • 推荐使用:静态内部类 或 枚举方式,优雅、安全、简洁。
  • 避免使用懒汉式无同步版本,会引入并发问题。
  • 单例并非银弹,合理场景下使用,配合线程安全设计尤为关键。

建议:每当你写出一个 new 的时候,不妨思考下:“这个类是不是该是一个单例?”

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐