Java 单例模式

单例模式简介

单例模式(Singleton Pattern)是 Java 中最简单的创建型设计模式之一。它保证一个类仅有一个实例,并提供一个全局访问点。

核心思想

  • 私有化构造器:不允许外部通过 new 创建实例。
  • 自己创建唯一实例:类内部保存一个静态的实例引用。
  • 提供静态访问方法:向外界返回这个唯一实例。

优点

  • 节省内存:只创建一个实例,避免频繁创建和销毁对象。
  • 全局访问:方便对共享资源进行统一控制(如配置信息、线程池、数据库连接池)。

缺点

  • 没有接口,扩展困难。
  • 与单一职责原则冲突:一个类既负责业务逻辑,又负责管理自己的实例。
  • 不适用于变化频繁的对象。

适用场景

  • 需要频繁创建和销毁的对象(如工具类)。
  • 对象占用资源较多,但又要全局共享(如数据库连接池)。
  • 需要严格控制访问权限的资源(如日志对象)。

实现单例的几种方式

以下五种实现方式涵盖了从简单到复杂、从线程不安全到安全的常见写法。

饿汉式(Eager Initialization)

public class Singleton {
    // 类加载时立即创建实例
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

特点

  • 线程安全(由类加载机制保证)。
  • 没有延迟加载,如果从未使用过该实例,会造成内存浪费。

适用场景:确定一定会使用该实例,且创建开销不大的情况。

懒汉式(Lazy Initialization)—线程不安全

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

特点

  • 延迟加载,第一次调用时才创建。
  • 线程不安全:多线程环境下可能创建多个实例。

适用场景:仅适用于单线程环境,实际开发中几乎不用。

懒汉式(线程安全)—同步方法

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

特点

  • 使用 synchronized 保证线程安全。
  • 每次调用 getInstance() 都会同步,性能较低。

适用场景:对性能要求不高,且实例创建不频繁的情况。

双重检查锁(Double-Checked Locking)

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;
    }
}

特点

  • 线程安全,同时兼顾性能。
  • 使用 volatile 关键字防止指令重排序(new Singleton() 不是原子操作)。
  • 代码稍复杂,是懒汉式的最佳实践之一。

适用场景:对性能有要求,且需要延迟加载的场景。

静态内部类(Initialization-on-demand Holder)

public class Singleton {
    private Singleton() {}

    // 静态内部类,不会在外部类加载时被加载
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

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

特点

  • 线程安全(由 JVM 类加载机制保证)。
  • 延迟加载:只有调用 getInstance() 时才会加载内部类,从而创建实例。
  • 代码简洁,效率高。

适用场景:推荐使用,兼顾了延迟加载和线程安全。

枚举单例(Enum Singleton)

public enum Singleton {
    INSTANCE;

    // 可以添加其他方法
    public void doSomething() {
        // ...
    }
}

特点

  • 线程安全,且能防止反序列化重新创建对象(枚举默认序列化机制保证)。
  • 写法最简单,但不够灵活(不能继承其他类)。

适用场景:需要防止反射和反序列化破坏的单例,或实例非常简单时。


关键知识点补充

1.synchronized 的作用

  • 修饰静态方法:锁住的是当前类的 Class 对象。
  • 修饰实例方法:锁住的是当前实例对象。
  • 同步代码块:更精细的控制,提高性能。

2. volatile 的作用

  • 保证可见性:一个线程修改 volatile 变量后,新值立即对其他线程可见。
  • 禁止指令重排序:防止在双重检查锁中,由于对象初始化时的指令重排序导致其他线程拿到未完全初始化的对象。

3. 反射和序列化对单例的破坏

  • 反射可以通过 setAccessible(true) 调用私有构造器,创建新实例。
  • 序列化会通过反序列化生成新对象(除非实现 readResolve() 方法)。

防御措施

  • 在构造器中判断实例是否已存在,若存在则抛出异常。
  • 枚举单例天然防御反射和序列化。

总结

实现方式 线程安全 延迟加载 性能 推荐度
饿汉式 ⭐⭐⭐
懒汉式(线程不安全) 高(单线程)
懒汉式(同步方法) ⭐⭐
双重检查锁 ⭐⭐⭐⭐
静态内部类 ⭐⭐⭐⭐⭐
枚举 ⭐⭐⭐⭐

推荐选择

  • 一般情况下,使用静态内部类方式最稳妥。
  • 如果确定实例一定会被使用,且无需延迟加载,可使用饿汉式
  • 需要防止反射破坏时,考虑枚举单例
posted @ 2022-07-15 09:29  克峰同学  阅读(62)  评论(0)    收藏  举报