在并发环境中创建单例的最佳实践?

时间:2013-10-12 00:02:56

标签: java multithreading concurrency thread-safety

我正在寻找类似于Is it appropriate to use AtomicReference.compareAndSet to set a reference to the results of a database call?但有不同要求的问题的答案。

目标是仅创建一次ObjectWithSideEffectConstructor实例,以避免重复的副作用。构造必须在setUp()中进行。多个线程将调用setUp()。类似地,将有一个tearDown()用于从对象中回收资源,这里省略了。问题:实现目标的最佳做法是什么?

仅仅使用AtomicReference是不够的,因为构造函数将首先被执行,因此副作用。

private static AtomicReference<ObjectWithSideEffectConstructor> ref =
  new AtomicReference<ObjectWithSideEffectConstructor>()

void setUp() {
  ref.compareAndSet(null, new ObjectWithSideEffectConstructor());
}

使用Is it appropriate to use AtomicReference.compareAndSet to set a reference to the results of a database call?的答案无效,因为volatile缺乏同步。将有多个线程进入if的窗口。

private static volatile ObjectWithSideEffectConstructor obj;

void setUp() {
  if (obj == null) obj = new ObjectWithSideEffectConstructor();
}

简单修复就是

private static ObjectWithSideEffectConstructor obj;
private static final Object monitor = new Object();

void setUp() {
  synchronized (monitor) {
    if (obj == null) obj = new ObjectWithSideEffectConstructor();
  }
}

类似地,具有易失性监视器的DCL可以提供更好的读取性能。但两者都需要一定程度的同步,因此预计性能会更差。

我们也可以使用FutureTask。它更有效,因为一旦创建了对象,后续的FutureTask.get()将无阻塞地返回。但它肯定比synchronized复杂得多。

private static final AtomicReference<FutureTask<ObjectWithSideEffectConstructor>> ref =
  new AtomicReference<FutureTask<ObjectWithSideEffectConstructor>>();

void setUp() {
  final FutureTask<ObjectWithSideEffectConstructor> future =
    new FutureTask<ObjectWithSideEffectConstructor>(
      new Callable<ObjectWithSideEffectConstructor>() {
        @Override
        public ObjectWithSideEffectConstructor call() throws InterruptedException {
          return new ObjectWithSideEffectConstructor();
        }
      }
    );
  if (ref.compareAndSet(null, future)) future.run();
  ref.get().get();
}

感谢您的建议。

6 个答案:

答案 0 :(得分:3)

如果您正在谈论threadsafelazy initialization singleton,这里有一个很酷的代码模式,可以完成 100%线程安全延迟初始化而无需任何同步代码< /强>:

public class MySingleton {

     private static class MyWrapper {
         static MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return MyWrapper.INSTANCE;
     }
}

这种编码模式称为Initialization-on-demand holder idiom。它只会在调用getInstance()时实例化单例,并且它是100%线程安全的!这是经典之作。

它的工作原理是因为类加载器有自己的同步来处理类的静态初始化:保证在使用类之前所有静态初始化都已完成,并且在此代码中,类仅在{{1}内使用方法,所以当加载类加载内部类时

答案 1 :(得分:1)

使用Enum在Java 5或更高版本中实现Singleton:

Enum是线程安全的,通过Enum实现Singleton可确保您的单例即使在多线程环境中也只有一个实例。 让我们看一个简单的实现:

public enum SingletonEnum 
{
    INSTANCE;
    public void doStuff()
    {
        System.out.println("Singleton using Enum");
    }
}

// How to use in other classes
public static void main(String[] args) 
{
    SingletonEnum.INSTANCE.doStuff();
}

答案 2 :(得分:1)

始终对单例使用枚举类型,它不仅优雅地强制执行单例,还可以防止常见的编程错误,例如当单例从其超类继承clone()方法并且程序员忘记使用私有方法覆盖它时宣言。或者当您忘记覆盖deserialisable,并允许程序员序列化您的单例,声明一个新实例,然后反序列化旧实例。

或者,如果使用静态工厂模式,则可以声明实例字段瞬态并使用readresolve方法。如果你可以在设计过程的后期改变你的想法,那么这就提供了灵活性。

信用:基于有效Java的答案,J Bloch(第3项),每本Java程序员都应该阅读,拥有和定期参考的书......

答案 3 :(得分:0)

我假设你只想要一个 ObjectWithSideEffectConstructor。这里有一个问题,1)它是否会发生两次你想要避免的副作用,或者2)你只需要一致(单例)引用。

无论哪种方式,synchronized都是一个很好的标准选项。它会使构建的其他线程保持第二个实例,而第一个线程处于安装状态。

如果你处于情况1),可能需要使用synchronized。如果启动后 的性能非常重要,您可以考虑使用AtomicReference.get()快速路径之前同步部分,以便在启动后避免同步部分完成了。

如果你处于情境2),那么 - 你的问题并不是很清楚 - 有施工的副作用,但你不关心重复这个 - 只要客户代码只“看到”一致的单一引用。

在第二种情况下,可以使用AtomicReference.get()来检查它是否已经初始化,如果是,则返回。然后,线程将进入“竞争部分”,在那里他们将构造(可能是多个)ObjectWithSideEffectConstructor。最后,会有一个compareAndSet,这样只有一个线程设置单例..失败的线程会回落到AtomicReference.get()以获取正确的单例。

Performancewise,对AtomicReference的单个调用比synchronized块快 - 但我不确定是否通过双重和三重检查&amp;构建不需要的副作用物体,第二种方法是。一个简单的synchronized块也可能更简单&amp;更快。

我有兴趣看一些测量结果。

答案 4 :(得分:0)

同步方法将是最佳选择。如果您确实需要性能,则需要重构代码以进行单线程预初始化。使用任何其他形式都会导致副作用,如singleton pattern

中所述

答案 5 :(得分:0)

对于它的价值,FutureTask方法实际上并不需要所有代码;不需要AtomicReference,也不应该同时调用 run() get() 。所以你可以稍微简化一下:

private static final Future<ObjectWithSideEffectConstructor> future =
  new FutureTask<>(
    new Callable<ObjectWithSideEffectConstructor>() {
      @Override
      public ObjectWithSideEffectConstructor call() throws InterruptedException {
        return new ObjectWithSideEffectConstructor();
      }
    }
  );

void setUp() {
  future.run(); // or future.get(), if you want to get any exception immediately
}

此外,使用Java 8,可以更简单地编写初始化表达式;以上可以简化为:

private static final Future<ObjectWithSideEffectConstructor> future =
  new FutureTask<>(ObjectWithSideEffectConstructor::new);

void setUp() {
  future.run(); // or future.get(), if you want to get any exception immediately
}