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

时间:2015-05-15 17:27:23

标签: java multithreading lazy-initialization lock-free wait-free

要执行无锁且无等待的延迟初始化,请执行以下操作:

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;
   }
}

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

是否有人建议使用另一种无锁延迟初始化模式,这会降低两个并发线程创建两个昂贵对象的可能性?

5 个答案:

答案 0 :(得分:22)

如果你想要真正的锁定自由,你将不得不做一些旋转。你可以拥有一个线程'获胜'创作权利,但其他人必须旋转直到它准备就绪。

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;
}

这显然存在繁忙旋转的问题(你可以在那里睡觉或产量),但我可能仍然会推荐Initialization on demand

答案 1 :(得分:3)

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

// 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;
}

这是一种非常常见的模式,特别是对于懒惰的单身人士。 Double checked locking最小化synchronized块实际执行的次数。

答案 2 :(得分:0)

我可能会选择懒惰的init Singleton模式:

private Foo() {/* Do your heavy stuff */}

private static class CONTAINER {
 private static final Foo INSTANCE = new Foo();
}

public static Foo getInstance() {
 return CONTAINER.INSTANCE;
}

我实际上没有看到任何使用AtomicReference成员字段的原因。

答案 3 :(得分:0)

如何使用另一个volatile变量锁定? 您可以使用新变量进行双重锁定吗?

答案 4 :(得分:-1)

我不确定最终结果是否应该以性能为中心,如果下面的是,则不是解决方案。请你检查两次,例如,在第一次检查后调用thread.sleep方法,随机mili秒小于100毫秒。

private AtomicBoolean canWrite = new AtomicBoolean(false);  
private volatile Foo foo; 
public Foo getInstance() {
   if(foo==null){
          Thread.Sleep(getRandomLong(50)) // you need to write method for it
         if(foo==null){
            foo = new Foo();
      }
   }
   return foo;
}