这个单例模式线程安全吗?

时间:2014-10-30 19:13:24

标签: java thread-safety singleton

我有一个单例服务器实例,我很好奇我的代码是否是线程安全的。 I've read关于不同的单例模式,我认为通常的方法是double-checked locking模式,如下所示:

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

这被认为是设置/获取单身的有效线程安全方式。我读过,获取和设置单例的最简单方法是lazy instantiation,如下所示:

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

现在我想知道的是我的变体是否是线程安全的。我的代码:

public static void startServer(int listeningPortNumber) throws IOException {
    if (server != null) {
        throw new IOException("Connection exists");
    }

    server = new Server(listeningPortNumber);
}

我的代码与上面的 lazy instantiation 模式非常相似,但我看不出我的代码是如何不是线程安全的。有没有我没看到的东西,或者这实际上是有效的代码吗?


参考:http://www.javaworld.com/article/2073352/core-java/simply-singleton.html

4 个答案:

答案 0 :(得分:6)

这不安全。

想象一下如果两个线程同时调用startServer(或者足够接近它)会发生什么:

  1. 线程A检查server != null,并看到server为空 - 因此它不会抛出异常
  2. 线程B执行相同的操作
  3. 线程A现在实例化new Server(listeningPortNumber);
  4. 线程B做同样的事情,并且可能在第二次实例化时发生了不好的事情
  5. 如果server不是volatile,则问题更严重,因为您甚至不再需要交错 - 线程A实例化的可能性很小new Server(...),但是线程B很长时间(可能永远)都没有看到对server字段的写入,因为它没有刷新到主存储器。

    但由于交错,即使servervolatile,该方法也很有效。

答案 1 :(得分:3)

不,懒惰的单例模式不是线程安全的。

如果你想在Java中使用线程安全版本的Singleton模式,你应该实现Bill Pugh的解决方案。代码在这里:

public class OptimalSingleton
{
  protected OptimalSingleton()
  {
    // ...
  }

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

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

关于SO的更多信息:Singleton pattern (Bill Pugh's solution)

答案 2 :(得分:3)

您的代码不是线程安全的。

假设您有两个线程,A和B调用startServer(...),并且在这些调用之前,服务器尚未初始化。 A传递一个值(a),B传递另一个值(b)到该方法。您唯一知道的是在每个线程中定义了操作顺序。

以下是可能的:

A: check server != null  (false)
B: check server != null  (false)
A: server = new Server(a)
B: server = new Server(b)
显然,这违反了单身人士。线程A将看到一个服务器初始化为与传入的值不同的值,并且从未抛出异常。

答案 3 :(得分:1)

您的代码不是线程安全的! 假设2个不同的线程同时调用startServer方法 两者都看到服务器变量为null,因此将跳过if块 每个线程都创建一个Server实例!因此,您将在应用程序中以2个服务器实例结束。