文章

Java 单例模式详解:从饿汉式到双检锁与静态内部类

为什么需要单例模式

在软件开发中,单例模式是一种常用的设计模式,用于确保一个类只有一个实例,并提供全局访问点。这在需要统一管理某些资源、配置文件或全局状态时非常有用。例如,数据库连接池日志管理器等都可以用单例模式实现,以避免不必要的实例创建和资源浪费。

实现思路

单例模式的核心思想是限制实例化次数并提供一个全局的访问点。我们有多种方式可以实现单例模式,常见的方法包括饿汉式(Eager Initialization)和多种懒汉式(Lazy Initialization)方法。下面我们按顺序进行介绍。

具体实现方式

饿汉式(Eager Initialization)

饿汉式在类加载时就创建实例,确保线程安全,但可能会浪费资源。

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

    /**
     * 私有构造函数,防止实例化
     */
    private Singleton() {
    }

    /**
     * 单例的外部入口
     *
     * @return {@link Singleton }
     */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

饿汉式虽然保证了线程安全,但由于实例在类加载时就创建,可能会导致资源浪费,特别是在程序启动时不需要立即使用单例实例的情况下。

传统的同步锁懒汉式(Synchronized Lazy Initialization)

懒汉式是在第一次调用 getInstance 方法时才创建实例,从而实现延迟加载。

public class Singleton {
    private static Singleton instance;

    /**
     * 私有构造函数,防止实例化
     */
    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        //第一次尝试获取时,创建实例
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

虽然这种方式实现了延迟加载,但在多线程环境下需要同步,这会带来性能开销。每次调用 getInstance 方法时,都会进行同步检查,这在实例已经存在时显得不必要。

双检锁懒汉式(Double-Checked Locking Lazy Initialization)

为了解决性能问题,我们可以采用双检锁(Double-Checked Locking)方式。双检锁在第一次检查是否实例化时不进行同步,仅在实例为空时才进入同步块进行第二次检查,从而确保线程安全并提升性能。

public class Singleton {
    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 关键字来确保内存可见性。虽然这种方式比传统同步锁懒汉式更高效,但实现起来相对复杂

静态内部类懒汉式(Static Inner Class Lazy Initialization)

为了解决双检锁的复杂性,我们可以采用静态内部类方式。静态内部类利用类加载机制,确保线程安全,同时提供延迟加载。

public class Singleton {
    private Singleton() {
    }

    private static class SingletonHelper {
        private static final Singleton INSTANCE = new Singleton();
    }

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

静态内部类方式在类加载时不会立即创建实例,而是在调用 getInstance 方法时才加载 SingletonHelper 类并创建实例。这种方式不仅保证了线程安全,还实现了延迟加载,减少了内存开销,同时也避免了使用双检锁时的复杂性。

总结

单例模式是确保一个类只有一个实例的有效方法。通过对饿汉式和三种懒汉式的介绍,我们可以看到每种方式的优缺点及其改进思路。选择哪种方式需要取决于具体的使用场景和需求。

希望这篇分享对你有所帮助!如果你有任何问题或建议,欢迎在评论区留言,与我共同交流探讨。

License:  CC BY 4.0