【问题标题】:Why is hungry singleton pattern thread-safe为什么饥饿的单例模式是线程安全的
【发布时间】:2018-03-27 04:50:13
【问题描述】:

这里是单例设计模式的实现之一:The Hungry implementation。

我了解到这种方式既可以只创建一个实例,又可以保证线程安全。

很容易理解只有一个实例,因为实例只是在类加载过程中创建的。

但是这怎么可能是线程安全的呢?在线文档说这也是因为实例是在加载类时创建的。但我不明白,这个实例如何在多线程情况下保持同步?这对我来说很模糊。有人能回答一下吗,先谢谢了。

  public class Hunger {
        private static Hunger instance = new Hunger();

        private Hunger() {}    

        public static Hunger getInstance() {
            return instance; //When multi threads call this, there maybe a problem since only instance is provided.
        }
    }

【问题讨论】:

  • 实例未同步。
  • 那么它是线程安全的吗?我很困惑。
  • getInstance 是
  • 我猜你可以说是因为没有非静态方法...
  • @doctorwhorm 我认为您对(1)获取对象和(2)使用对象的线程安全性感到困惑。这些是完全分开的。单例模式大约是 (1)。对象如何管理 (2) 是完全独立的事情,Hungry/Lazy 对此无话可说。

标签: java design-patterns thread-safety singleton


【解决方案1】:

为了确保线程安全地初始化单例,您可以使用一种特殊的类,称为枚举。例如:

enum Hungry{
    INSTANCE;
    private static Random rd;

    static {
        rd = new Random();
        System.out.println("Initializing object...");
    }
    public Integer nextNum() {
        return new Random().nextInt(10);
    }

}

public class LazyEval {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Start...");

        Thread.sleep(1000);
        System.out.println("First call");
        System.out.println(Hungry.INSTANCE.nextNum());
        Thread.sleep(1000);
        System.out.println("Second Call");
        System.out.println(Hungry.INSTANCE.nextNum());

    }
}

你会得到这样的输出:

Start... 
First call 
Initializing object... 
6 
Second Call 0

这是因为,当您第一次访问 INSTANCE 时,您的 JVM 将初始化静态块上的所有字段。通过这种方式,您可以存档实时延迟初始化并避免竞争条件。

【讨论】:

  • 喜欢你使用枚举的方式。
  • 我很乐意,但我的低名声只能让我接受,很抱歉。
【解决方案2】:

延迟初始化会导致线程同步问题。 (惰性初始化意味着在调用 createInstance() 或 getInstance() 时创建单例实例)。 但是静态 Hunger 实例的初始化时间比线程创建时间快。 这不是惰性初始化。

但这不是完全同步。 因为它仅在创建实例时可用。 也许在线文档说这仅在创建单例实例时可用。 所以在放任何公共资源成员时必须使用线程锁对象。

【讨论】:

  • 那么它是线程安全的吗?我的意思是如果有10个线程要同时获取实例,那么上面的代码谁会得到唯一的一个实例呢?这会导致同步问题还是正常工作?
  • 如果你只是得到一个单例实例,那么它将是线程安全的。但是你应该注意每 10 个线程将访问任何单例类的成员,那么它不是线程安全的。所以总结为2句。 "你的单例实例将是线程安全的" , "但是如果你访问任何成员那么它不会对该成员线程安全"
  • 然后你说'谁会得到上面代码的唯一一个实例?但我无法理解。也许在每个线程中谁将首先获得单例实例并不重要。
  • p.s : 当另一个所有线程都关闭时,您必须在主线程释放这个单例实例。
【解决方案3】:

Singleton 的线程问题在于确保您只初始化一次,并且始终向提出请求的每个人提供相同的对象。如果您将初始化推迟到第一次调用:

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

你可以同时在if块中得到两个线程,调用new两次,坏事接踵而至。

如何同步Hunger对象本身是一个单独的问题;这与 Singleton 模式无关。

【讨论】:

  • 您的代码通常是惰性模式,并且在您在方法之前添加同步之前它不是线程安全的(至少我认为是)。但我很好奇的是饥饿模式。
  • @doctorwhorm 你已经展示了上面的饥饿模式,在加载类时创建对象。我的示例是向您展示另一种创建单例的方法,该方法是 Lazy、不 Hungry 且不是线程安全的。这不是你要求的吗?
  • 对不起,我可能表达得不好。我只是想问一下饥饿模式是线程安全的。
  • 饥饿模式对于get对象引用是线程安全的;对对象方法或成员的任何特定访问是否是线程安全的,取决于该对象。
猜你喜欢
  • 2010-11-12
  • 2021-06-25
  • 2014-01-24
  • 1970-01-01
  • 2012-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-23
相关资源
最近更新 更多