无锁和无等待线程安全延迟初始化
Lock-free and wait-free thread-safe lazy initialization
为了执行无锁和无等待延迟初始化,我执行以下操作:
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操作设置它,这导致浪费资源。
有没有人建议另一种无锁惰性初始化模式,它可以降低两个并发线程创建两个昂贵对象的可能性?
如果你想要真正的无锁,你将不得不做一些旋转。您可以拥有一个线程 'win' 创建权,但其他线程必须旋转直到准备就绪。
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。
我认为您需要为对象创建本身进行一些同步。我会这样做:
// 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
块实际执行的次数。
我不确定最终结果是否应该以性能为中心,如果是,下面不是解决方案。例如,您能否检查两次,并在第一次检查调用 thread.sleep 方法后随机 mili 秒小于 100 mili 秒。
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;
}
我可能会选择惰性初始化单例模式:
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 成员字段。
使用另一个 volatile
变量来锁定怎么样?
你可以用新变量做双锁吗?
为了执行无锁和无等待延迟初始化,我执行以下操作:
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操作设置它,这导致浪费资源。
有没有人建议另一种无锁惰性初始化模式,它可以降低两个并发线程创建两个昂贵对象的可能性?
如果你想要真正的无锁,你将不得不做一些旋转。您可以拥有一个线程 'win' 创建权,但其他线程必须旋转直到准备就绪。
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。
我认为您需要为对象创建本身进行一些同步。我会这样做:
// 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
块实际执行的次数。
我不确定最终结果是否应该以性能为中心,如果是,下面不是解决方案。例如,您能否检查两次,并在第一次检查调用 thread.sleep 方法后随机 mili 秒小于 100 mili 秒。
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;
}
我可能会选择惰性初始化单例模式:
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 成员字段。
使用另一个 volatile
变量来锁定怎么样?
你可以用新变量做双锁吗?