在 Java 中,实现线程安全的单例模式有多种方式。以下是几种常用的线程安全单例模式实现:
1. 饿汉式(线程安全,类加载时初始化)
特点:简单且线程安全,但如果实例过于占用资源且程序可能不使用它,会造成内存浪费。
public class Singleton { private static final Singleton INSTANCE = new Singleton(); // 私有构造方法,防止外部创建实例 private Singleton() {} // 全局访问点 public static Singleton getInstance() { return INSTANCE; } }
-
优点:实现简单,类加载时就完成实例化,线程安全。
-
缺点:即使不使用也会实例化,占用资源。
2. 懒汉式(线程不安全,延迟加载)
特点:实例在需要时创建,但默认实现线程不安全。
public class Singleton { private static final Singleton INSTANCE = new Singleton(); // 私有构造方法,防止外部创建实例 private Singleton() {} // 全局访问点 public static Singleton getInstance() { return INSTANCE; } }
-
缺点:在多线程环境中可能会创建多个实例,不推荐在并发场景下使用。
3. 懒汉式 + synchronized(线程安全,但性能较差)
特点:通过 synchronized
关键字确保线程安全,但性能较低。
public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
-
优点:线程安全,延迟加载。
-
缺点:
synchronized
会影响性能,每次调用都要获取锁。
4. 双重检查锁(推荐,性能较高)
特点:结合延迟加载与线程安全,减少同步开销。
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; } }
-
优点:线程安全,延迟加载,性能较好。
-
缺点:实现稍显复杂。
5. 静态内部类(推荐,线程安全,延迟加载)
特点:利用类加载机制实现延迟加载,同时天然线程安全。
public class Singleton { private Singleton() {} // 静态内部类,在加载时初始化 Singleton 实例 private static class Holder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }
-
优点:
-
延迟加载:
Holder
类只有在第一次调用getInstance
方法时才会加载并初始化实例。 -
线程安全:JVM 类加载机制保证线程安全。
-
6. 枚举单例(最简单,线程安全)
特点:利用枚举的特性实现单例。
public enum Singleton { // 定义一个枚举元素,代表 Singleton 的唯一实例 INSTANCE; // 单例类中的其他方法或变量 private int value; // 初始化方法,可以在枚举的构造方法中进行初始化 Singleton() { this.value = 42; // 假设默认值为 42 } // 对外暴露的方法,可以操作或获取内部状态 public void someMethod() { System.out.println("Singleton method invoked. Current value: " + value); } // Getter 和 Setter 方法 public int getValue() { return value; } public void setValue(int value) { this.value = value; } }
public class Main { public static void main(String[] args) { // 获取单例实例 Singleton instance = Singleton.INSTANCE; // 调用方法 instance.someMethod(); // 输出: Singleton method invoked. Current value: 42 // 修改状态 instance.setValue(100); System.out.println("Updated value: " + instance.getValue()); // 输出: Updated value: 100 } }
-
优点:
-
线程安全:枚举本身是线程安全的。
-
防止反序列化创建新实例:枚举类型保证了单例。
-
-
缺点:不能进行懒加载,但推荐作为一种标准单例实现方式。
总结
实现方式 | 是否线程安全 | 是否延迟加载 | 优点 | 缺点 |
---|---|---|---|---|
饿汉式 | 是 | 否 | 简单,线程安全 | 不使用时会浪费资源 |
懒汉式 | 否 | 是 | 延迟加载 | 多线程环境可能不安全 |
synchronized 懒汉式 | 是 | 是 | 简单,线程安全 | 性能差 |
双重检查锁 | 是 | 是 | 性能较好,线程安全 | 实现复杂 |
静态内部类 | 是 | 是 | 延迟加载,线程安全,推荐使用 | 适用性广,缺点少 |
枚举单例 | 是 | 否 | 简洁,线程安全,防反序列化和反射攻击 | 不能延迟加载 |
推荐实现
-
常规场景:使用 静态内部类 或 枚举单例,线程安全且性能优越。
-
对性能要求极高:可以使用 双重检查锁 实现。