Ответ 1
См. Suppliers.memoize(Supplier)
для ленивого инициализации значения.
Будучи впечатлен Guava вычислительная функция карты, я ищу своего рода "вычислительную ссылку" - ленивую реализацию ссылок на загрузку, которая параллельная Гуава простота использования, под которым я имею в виду, что она обрабатывает все операции блокировки, загрузки и обработки исключений под капотом, только подвергая метод get()
.
После краткого поиска ничего не появилось, я быстро перевернул свое собственное в качестве доказательства концепции:
public abstract class ComputingRef<T> implements Callable<T> {
private volatile T referent = null;
private Lock lock = new ReentrantLock();
public T get() {
T temp = referent;
if (temp == null) {
lock.lock();
try {
temp = referent;
if (temp == null) {
try {
referent = temp = call();
}
catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
}
else {
throw new RuntimeException(e);
}
}
}
}
finally {
lock.unlock();
}
}
return temp;
}
}
Этот ComputingRef
может быть анонимно расширен для реализации call()
, который функционирует как метод factory:
ComputingRef<MyObject> lazySingletonRef = new ComputingRef<MyObject>() {
@Override
public MyObject call() {
//fetch MyObject from database and return
}
};
Я не уверен, что эта реализация является оптимальной, но она демонстрирует, что мне нужно.
Позже я нашел этот пример из T2 Framework, который выглядит более сложным.
Теперь мои вопросы:
EDIT: Обновлена моя реализация, чтобы использовать локальную переменную, предложенную @irreputable answer - пожалуйста, повысьте ее, если вы найдете приведенный выше пример полезно.
См. Suppliers.memoize(Supplier)
для ленивого инициализации значения.
В любом случае, вот как я это сделаю (а потом я буду беспокоиться о производительности позже):
public abstract class ComputingRef<T> implements Callable<T> {
private final AtomicReference<T> ref = new AtomicReference<T>();
public T get() {
if (ref.get() == null) {
try {
final T newValue = call();
if (ref.compareAndSet(null, newValue)) {
return newValue;
}
} catch (final Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new RuntimeException(e);
}
}
}
return ref.get();
}
}
Единственный "промах" с этим подходом состоит в том, что существует условие гонки, которое может приводить к множественным экземплярам объекта референта (например, если ComputingRef
делится на большое количество потоков, все из которых попадают get()
в в то же время). Если создание экземпляра класса референта настолько дорого или вы хотите избежать множественной инстанцировки любой ценой, я бы пошел с вашей двойной проверкой блокировки.
Вы также должны убедиться, что референтный объект очищается после себя. В противном случае, если сбой compareAndSet()
завершен, убедитесь, что вы выполнили необходимую очистку.
(Обратите внимание, что если референт должен быть одиночным, тогда я бы использовал инициализацию по требованию владельца idiom.)
Это хорошая старая двукратная блокировка идиомы. Вы должны добавить локальную переменную для производительности. В вашем случае у вас есть 2 изменчивых чтения на быстром пути (когда установлен референт). Проверьте http://en.wikipedia.org/wiki/Double-checked_locking