线程安全在Java中实现单例模式的有效方法? [复制]

Posted

技术标签:

【中文标题】线程安全在Java中实现单例模式的有效方法? [复制]【英文标题】:Thread Safe Efficient way to implement singleton pattern in Java? [duplicate] 【发布时间】:2011-05-27 19:42:06 【问题描述】:

可能重复:Efficient way to implement singleton pattern in Java

我正在阅读这个Best Singleton Implementation In Java,但它不是线程安全的。

根据维基:

if(singleton==null) synchronized(Singleton.class) // this is needed if two threads are waiting at the monitor at the // time when singleton was getting instantiated if(singleton==null) singleton= new Singleton();

但是 Find Bugs 实用程序在这方面给出了两个错误: 1.双重空检查。 2. 静态字段延迟初始化不正确。

什么是最好的方法,

是否正确:

synchronized (Singleton.class) 
if (singleton== null) 
singleton= new Singleton();


【问题讨论】:

这是上述问题的重复;有关详细信息,请参阅该问题。但我不确定这个问题是否有这些有用的链接,所以:About double-checked locking in Java 链接到 these two Java 5 更新。另请参阅 Wikipedia's article on double-checked locking。但对于您的问题的实际答案,请参阅上面链接的问题。 【参考方案1】:

制作延迟加载单例的最有效/最简单的方法就是

enum Singleton 
   INSTANCE

注意:不需要锁定,因为类加载是线程安全的。该类默认为final,不能通过反射调用构造函数。在使用实例或使用类之前,不会创建实例。如果您担心该类可能会被意外使用,您可以将单例包装在内部类中。

final class Singleton 
    private Singleton()  
    static class SingletonHolder 
        static final Singleton INSTANCE = new Singleton();
    
    public static Singleton getInstance() 
        return SingletonHolder.INSTANCE;
    

恕我直言,您必须非常偏执才能认为这是一个更好的解决方案。

【讨论】:

在您的第二个代码中,您没有使用enum,但您使用的是class,所以我很困惑如何使用它。我想做一个枚举来初始化一个类,下次每次调用那个对象。我该怎么做。你能详细举个例子吗 @Manish 在这种情况下,使用enum,就像我在第一个示例中所做的那样。 是的,没关系,但我怎样才能只初始化一个类并在下次使用enum 时使用该实例。那是我的困惑。我在哪里看到enum Singleton INSTANCE 这么多,但不知道如何调用和初始化对象 您可以在代码中的任何位置使用Singleton.INSTANCE。以线程安全的方式调用一次的用于初始化此实例的代码将位于 enum 的构造函数中 @PeterLawrey 你没有忘记你的代码 sn-p 中的私有构造函数 Singleton() 吗?【参考方案2】:

关于这个问题已经写了很多。是的,简单的双重检查锁定模式并不安全。但是您可以通过将静态实例声明为 volatile 来使其安全。新的 Java 内存模型规范在处理 volatile 时为编译器添加了一些代码重新排序限制,因此原来的风险已经消失。

反正我在创建实例时很少真正需要这种惰性,所以我通常只是在类加载时静态地创建它:

private static MyClass instance = new MyClass();

这是简短而清晰的。作为替代方案,如果你真的想让它变得懒惰,你可以利用类加载特性并这样做:

public class MyClass 
    private static class MyClassInit 
        public static final MyClass instance = new MyClass();
    

    public static MyClass getInstance() 
        return MyClassInit.instance; 
    
...

在您第一次调用 getInstance() 之前,不会加载嵌套类。

【讨论】:

【参考方案3】:

Efficient way to implement singleton pattern in Java 已接受答案中的第一个代码示例 线程安全的。 INSTANCE的创建由类加载器在第一次加载类时执行;它只执行一次,并且以线程安全的方式:

public final class Foo 

    private static final Foo INSTANCE = new Foo();

    private Foo() 
        if (INSTANCE != null) 
                throw new IllegalStateException("Already instantiated");
        
    

    public static Foo getInstance() 
        return INSTANCE;
    

(复制自What is an efficient way to implement a singleton pattern in Java?)

问题中的第二个代码示例是正确且线程安全的,但它会导致每次调用 getInstance() 时同步,从而影响性能。

【讨论】:

不保护私有构造函数是相当偏执的。我假设它避免使用反射创建另一个实例,还是停止内部类调用构造函数? 我正在讨论上一个问题的答案,所以我按原样复制了代码。我个人也会省略if (INSTANCE != null) 检查。 因此我认为是例外。 ;) 您好 Eli,从您的回答中我了解到 Foo 对象将只有一个副本。但是假设两个线程同时调用 getInstance() 函数。那么我相信会有一个比赛条件。如何避免这种情况。因此我们需要避免这种情况。请查看 [link]***.com/questions/15930633/… 以获得最终答案...

以上是关于线程安全在Java中实现单例模式的有效方法? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何使用工厂构造函数在 Dart 中实现单例模式?

python中实现单例模式

如何在Qt 中实现单例模式

如何在 C# 中实现单例设计模式? [复制]

Unity3D日常开发Unity3D中实现单例模式详解

设计模式:单例模式的三种创建方式及其各自的优缺点