要执行无锁且无等待的延迟初始化,请执行以下操作:
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操作设置它,这会浪费资源。
是否有人建议使用另一种无锁延迟初始化模式,这会降低两个并发线程创建两个昂贵对象的可能性?
答案 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;
}