首页  >  文章  >  Java  >  不要让你的单身人士崩溃!下面是如何在 Java 中使其线程安全

不要让你的单身人士崩溃!下面是如何在 Java 中使其线程安全

Patricia Arquette
Patricia Arquette原创
2024-11-02 09:55:02285浏览

Don’t Let Your Singleton Break! Here’s How to Make It % Thread-Safe in Java

在这篇文章中,我们将探索在 Java 中实现线程安全单例的几种方法,包括热切初始化双重检查锁定 内部静态类 方法。我们还将讨论为什么 Final 关键字有利于确保单例的完整性。

为什么使用单例?

当您在整个应用程序中恰好需要类的一个实例时,单例非常有用。常见用例包括管理共享资源,例如日志记录、配置或连接池。单例确保访问一个类的多个请求共享同一个实例,而不是创建新实例。

1. 热切初始化:最简单的单例

急切初始化模式在类加载时创建单例实例。这很简单并且确保了线程安全,因为实例是在 JVM 加载类时创建的。

public final class Singleton {
    // Instance is created at class loading time
    private static final Singleton INSTANCE = new Singleton();

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

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

热切初始化的优点和缺点

优点

  • 默认情况下简单且线程安全,因为 JVM 保证类初始化是线程安全的。
  • 无需同步或额外的复杂性。

缺点

  • 无论是否使用实例都会创建,如果永远不需要单例,这可能会导致资源浪费。

何时使用它:当单例类是轻量级的并且您确定它将在应用程序运行时使用它时,热切初始化是最好的。


2.带有双重检查锁定的延迟初始化

如果您想要延迟创建单例直到需要时(称为延迟初始化),双重检查锁定提供了一个线程安全的解决方案。它使用最少的同步并确保仅在第一次访问实例时才创建实例。

public final class Singleton {  // Marked as final to prevent subclassing

    // volatile ensures visibility and prevents instruction reordering
    private static volatile Singleton instance;

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {               // First check (no locking)
            synchronized (Singleton.class) {   // Locking
                if (instance == null) {        // Second check (with locking)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

为什么双重检查锁定有效

  1. 第一次检查:同步块之外的 if (instance == null) 检查允许我们避免每次调用 getInstance() 时锁定。这通过绕过初始化后未来调用的同步块来提高性能。

  2. 同步块:一旦实例为空,进入同步块可确保只有一个线程创建 Singleton 实例。到达这一点的其他线程必须等待,以防止竞争条件。

  3. 第二次检查:在同步块内,我们再次检查实例以确保当前线程等待时没有其他线程初始化它。这一双重检查可确保仅创建一个 Singleton 实例。

为什么需要易失性

可变关键字在双重检查锁定模式中至关重要,以防止指令重新排序。如果没有它,语句instance = new Singleton();在完全初始化之前,其他线程可能会显得已完成,从而导致返回部分构造的实例。易失性保证一旦实例非空,它就被完全构造并且对所有线程可见。

为什么 Final 是良好实践

这里使用final关键字来防止子类化。将 Singleton 类标记为 Final 有两个主要好处:

  1. 防止子类化:通过将类设为最终类,我们可以防止其他类扩展它。这确保了 Singleton 类只能存在一个实例,因为子类化可能会导致额外的实例,从而破坏单例模式。

  2. 信号不变性:final 向其他开发人员明确指示单例类是不可变的,不应扩展。这使得代码更容易理解和维护。

简而言之,final 增强了单例的完整性,并有助于避免子类化带来的意外行为。

双重检查锁定的优点和缺点

优点

  • 延迟初始化通过延迟创建直到需要时来节省资源。
  • 由于双重检查而导致的同步开销最小。

缺点

  • 稍微复杂一点,为了安全需要 volatility。
  • 对于更简单的单例需求(在急切初始化就足够的情况下)可能有点过分了。

何时使用它:当单例类是资源密集型且可能并不总是需要时,或者当需要考虑多线程环境中的性能时,此模式非常有用。


3. 内部静态类:更干净的延迟初始化替代方案

延迟初始化的另一种线程安全方法是内部静态类模式。这利用了 Java 的类加载机制,仅在需要时才初始化单例实例,无需显式同步。

public final class Singleton {
    // Instance is created at class loading time
    private static final Singleton INSTANCE = new Singleton();

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

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

内部静态类如何工作

在此模式中,SingletonHelper 类仅在第一次调用 getInstance() 时加载。这会触发 INSTANCE 的初始化,确保延迟加载而不需要同步块。

内部静态类的优点和缺点

优点

  • 线程安全,无需易失性或同步块。
  • 简单干净,设计上采用延迟初始化。

缺点

  • 对于不熟悉 Java 类加载机制的新开发人员来说稍微不太直观。

何时使用它:当您希望使用干净、可维护的代码进行延迟初始化时,请使用内部静态类模式。由于简单性和线程安全性,这通常是首选。


总结

我们研究了在 Java 中实现线程安全单例的三种流行方法:

  1. 急切初始化:最适合单例是轻量级的并且将始终使用的简单情况。
  2. 双重检查锁定:非常适合性能敏感、需要延迟初始化的多线程环境。
  3. 内部静态类:使用 Java 类加载行为进行延迟初始化的干净且线程安全的方法。

每种方法都有其优点并适合不同的场景。在您自己的项目中尝试一下,看看哪一个最适合您!如果您有首选方法或有任何问题,请在评论中告诉我。

编码愉快! ??‍??‍?

以上是不要让你的单身人士崩溃!下面是如何在 Java 中使其线程安全的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn