无锁和无等待线程安全延迟初始化

要执行无锁和无等待的惰性初始化,我执行以下操作:

private AtomicReference<Foo> instance = new AtomicReference<>(null);  

public Foo getInstance() {
   Foo foo = instance.get();
   if (foo == null) {
       foo = new Foo();                       // create and initialize actual instance
       if (instance.compareAndSet(null, foo)) // CAS succeeded
           return foo;
       else                                   // CAS failed: other thread set an object 
           return instance.get();             
   } else {
       return foo;
   }
}

除了一件事之外,它工作得很好:如果两个线程看到实例,它们都会创建一个新对象,并且只有一个线程幸运地通过CAS操作设置它,这导致资源浪费。null

有没有人建议使用另一种无锁的惰性初始化模式,该模式可以降低通过两个并发线程创建两个昂贵对象的可能性?


答案 1

如果你想要真正的锁自由,你将不得不做一些旋转。您可以让一个线程“赢得”创建权限,但其他线程必须旋转直到准备就绪。

private AtomicBoolean canWrite = new AtomicBoolean(false);  
private volatile Foo foo; 
public Foo getInstance() {
   while (foo == null) {
       if(canWrite.compareAndSet(false, true)){
           foo = new Foo();
       }
   }
   return foo;
}

这显然有其忙于旋转的问题(你可以在那里睡觉或屈服),但我可能仍然建议按需初始化


答案 2

我认为您需要为对象创建本身进行一些同步。我会做的:

// The atomic reference itself must be final!
private final AtomicReference<Foo> instance = new AtomicReference<>(null);
public Foo getInstance() {
  Foo foo = instance.get();
  if (foo == null) {
    synchronized(instance) {
      // You need to double check here
      // in case another thread initialized foo
      Foo foo = instance.get();
      if (foo == null) {
        foo = new Foo(); // actual initialization
        instance.set(foo);
      }
    }
  }
  return foo;
}

这是一种非常常见的模式,特别是对于懒惰的单例。双重检查锁定可最大程度地减少块实际执行的次数。synchronized