Вот еще один подход, основанный на семантике одноразового исполнителя.
Полное решение с кучей примеров использования можно найти на github (https://github.com/ManasjyotiSharma/java_lazy_init). Вот суть этого:
Семантика «One Time Executor», как следует из названия, имеет следующие свойства:
- Объект-обертка, который оборачивает функцию F. В текущем контексте F - это функция / лямбда-выражение, которое содержит код инициализации / деинициализации.
Оболочка предоставляет метод execute, который ведет себя как:
- Вызывает функцию F при первом вызове execute и кэширует выходные данные F.
- Если вызов двух или более потоков выполняется одновременно, только один «входит», а остальные блокируются, пока не завершится тот, который «вошел».
- Для всех других / будущих вызовов execute он не вызывает F, а просто возвращает ранее кэшированный вывод.
Доступ к кэшированному выходу можно безопасно получить из-за пределов контекста инициализации.
Это можно использовать как для инициализации, так и для неидемпотентной деинициализации.
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
/**
* When execute is called, it is guaranteed that the input function will be applied exactly once.
* Further it's also guaranteed that execute will return only when the input function was applied
* by the calling thread or some other thread OR if the calling thread is interrupted.
*/
public class OneTimeExecutor<T, R> {
private final Function<T, R> function;
private final AtomicBoolean preGuard;
private final CountDownLatch postGuard;
private final AtomicReference<R> value;
public OneTimeExecutor(Function<T, R> function) {
Objects.requireNonNull(function, "function cannot be null");
this.function = function;
this.preGuard = new AtomicBoolean(false);
this.postGuard = new CountDownLatch(1);
this.value = new AtomicReference<R>();
}
public R execute(T input) throws InterruptedException {
if (preGuard.compareAndSet(false, true)) {
try {
value.set(function.apply(input));
} finally {
postGuard.countDown();
}
} else if (postGuard.getCount() != 0) {
postGuard.await();
}
return value();
}
public boolean executed() {
return (preGuard.get() && postGuard.getCount() == 0);
}
public R value() {
return value.get();
}
}
Вот пример использования:
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
/*
* For the sake of this example, assume that creating a PrintWriter is a costly operation and we'd want to lazily initialize it.
* Further assume that the cleanup/close implementation is non-idempotent. In other words, just like initialization, the
* de-initialization should also happen once and only once.
*/
public class NonSingletonSampleB {
private final OneTimeExecutor<File, PrintWriter> initializer = new OneTimeExecutor<>(
(File configFile) -> {
try {
FileOutputStream fos = new FileOutputStream(configFile);
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(bw);
return pw;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
);
private final OneTimeExecutor<Void, Void> deinitializer = new OneTimeExecutor<>(
(Void v) -> {
if (initializer.executed() && null != initializer.value()) {
initializer.value().close();
}
return null;
}
);
private final File file;
public NonSingletonSampleB(File file) {
this.file = file;
}
public void doSomething() throws Exception {
// Create one-and-only-one instance of PrintWriter only when someone calls doSomething().
PrintWriter pw = initializer.execute(file);
// Application logic goes here, say write something to the file using the PrintWriter.
}
public void close() throws Exception {
// non-idempotent close, the de-initialization lambda is invoked only once.
deinitializer.execute(null);
}
}
Еще несколько примеров (например, одноэлементная инициализация, которая требует, чтобы некоторые данные были доступны только во время выполнения и, следовательно, не могли создать его экземпляр в статическом блоке), пожалуйста, обратитесь к ссылке на github, упомянутой выше.