Java中单例模式的几种实现方式总结

安卓135 次阅读10 分钟

单例模式(Singleton Pattern)是Java中最基本和最常用的设计模式之一。该模式确保一个类只有一个实例,并提供一个全局访问点来访问该实例。在需要频繁创建和销G毁对象或者需要一个全局唯一的对象(例如,线程池、缓存、日志对象等)的场景下,单例模式尤为重要。

以下总结了Java中几种常见的单例模式实现方式,并分析了各自的优缺点。

1. 饿汉式(Eager Initialization)

饿汉式在类加载时就立即创建实例,因此是线程安全的。

public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    private EagerSingleton() {}

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

优点:

  • 实现简单,代码可读性高。
  • 在类加载时就完成了实例化,避免了多线程同步问题,是线程安全的。

缺点:

  • 在类加载时就创建实例,即使后续从未使用过该实例,也会占用内存空间,可能造成资源浪费。

2. 懒汉式(Lazy Initialization)

懒汉式在第一次调用getInstance()方法时才创建实例。

2.1 基础懒汉式(非线程安全)

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

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

优点:

  • 实现了懒加载,只有在需要时才创建实例,节约了资源。

缺点:

  • 在多线程环境下是非线程安全的。如果多个线程同时进入if (instance == null)判断,并且此时instance都为null,那么将会有多个线程创建出多个实例,违背了单例模式的初衷。

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

通过在getInstance()方法上添加synchronized关键字,可以保证线程安全。

public class SynchronizedLazySingleton {
    private static SynchronizedLazySingleton instance;

    private SynchronizedLazySingleton() {}

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

优点:

  • 解决了多线程环境下的线程安全问题。

缺点:

  • 每次调用getInstance()方法时都需要进行同步,即使实例已经被创建。这会导致不必要的性能开销,尤其是在高并发场景下。

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

双重检查锁定是对线程安全的懒汉式的一种优化,旨在减少不必要的同步开销。

public class DoubleCheckedSingleton {
    private static volatile DoubleCheckedSingleton instance;

    private DoubleCheckedSingleton() {}

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

关键点:

  • 使用volatile关键字修饰instance变量,确保其可见性和禁止指令重排。
  • 进行两次if (instance == null)检查,第一次检查是为了避免在实例已存在的情况下进入同步代码块,从而提高性能。第二次检查是为了在同步代码块内部再次确认实例是否被创建,防止多个线程同时通过第一次检查后重复创建实例。

优点:

  • 兼顾了线程安全和懒加载的性能。只有在第一次创建实例时才会进入同步代码块,后续的调用将直接返回已创建的实例,性能较高。

缺点:

  • 实现相对复杂,容易出错。volatile关键字是必不可少的,否则可能由于指令重排导致获取到未完全初始化的对象。

4. 【推荐】静态内部类(Static Inner Class)

这是目前被广泛推荐的一种实现方式,它利用了JVM类加载机制来保证线程安全和懒加载。

public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {}

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

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

原理:

  • StaticInnerClassSingleton类被加载时,其静态内部类SingletonHolder并不会被加载。
  • 只有当第一次调用getInstance()方法时,JVM才会加载SingletonHolder类,并初始化INSTANCE
  • JVM在加载类时是线程安全的,因此保证了只有一个线程可以初始化INSTANCE

优点:

  • 实现了懒加载。
  • 由JVM保证线程安全,无需任何同步代码,性能高。
  • 实现简单,代码清晰。

5. 枚举(Enum)

这是《Effective Java》作者Joshua Bloch推荐的最佳实现方式。

public enum EnumSingleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("do something...");
    }
}

优点:

  • 实现最简单。
  • 天然线程安全。
  • 能有效防止通过反射和反序列化创建新的对象,从而破坏单例。

缺点:

  • 不具备懒加载的特性。

总结

实现方式 线程安全 懒加载 推荐指数 备注
饿汉式 ⭐⭐⭐ 实现简单,但可能造成资源浪费。
懒汉式(非线程安全) 仅限单线程环境使用。
懒汉式(同步方法) ⭐⭐ 性能开销较大,不推荐。
双重检查锁定 ⭐⭐⭐⭐ 实现较复杂,需正确使用volatile
静态内部类 ⭐⭐⭐⭐⭐ 兼具懒加载和线程安全,实现简单,推荐使用。
枚举 ⭐⭐⭐⭐⭐ 最简洁、最安全的实现方式,能防止反射和反序列化攻击。

在实际开发中,静态内部类枚举是两种最被推崇的单例模式实现方式。如果需要懒加载,静态内部类是绝佳的选择;如果不需要懒加载,或者对防止反射和反序列化有强烈的需求,枚举则是最简单且最安全的方式。

继续阅读

基于全文检索与主题相似度