Безликая и безжизненная поточно-надежная инициализация
Для выполнения незапираемой и ожидающей ленивой инициализации я делаю следующее:
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, что приводит к расходованию ресурсов.
Кто-нибудь предлагает другой незаметный ленивый шаблон инициализации, который уменьшает вероятность создания двух дорогостоящих объектов двумя параллельными потоками?
Ответы
Ответ 1
Если вам нужна истинная свобода блокировки, вам придется немного покрутиться. У вас могут быть права на создание нитей "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;
}
У этого, очевидно, есть проблемы с занятым вращением (вы можете спать или уступать там), но я, вероятно, по-прежнему рекомендую Инициализацию по требованию.
Ответ 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
блоков.
Ответ 3
Я бы, вероятно, пошел с ленивым стилем 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 для себя.
Ответ 4
Как насчет использования другой volatile
переменной для блокировки? Вы можете сделать двойной замок с новой переменной?
Ответ 5
Я не уверен, что конечный результат должен быть ориентирован на производительность или нет, если да ниже - это не решение. можете ли вы проверить дважды, например, и после первой проверки вызова метода 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;
}