Java FutureTask - многопоточный вызов get()
У меня есть следующие два метода в классе:
private MyDef myDef;
private FutureTask<MyDef> defFutureTask;
public synchronized void periodEviction() {
myDef = null;
}
public MyDef loadMyItems() {
// if it's not ready use a future - it will block until the results are ready
if (this.myDef == null) { // this will still not be thread safe
Callable<MyDef> callableDef = ()->{ return this.loadFromDatabase(); };
FutureTask<MyDef> defTask = new FutureTask<>(callableDef);
this.defFutureTask = defTask;
defFutureTask.run();
}
try {
// wait until's it's ready
this.myDef = this.qDefFuture.get();
} catch(InterruptedException e) {
log.error(this.getClass(), "Interrupted whilst getting future..");
} catch(ExecutionException e) {
log.error(this.getClass(), "Error when executing callable future");
}
return this.myDef;
}
Я хотел сделать следующее:
1) Выполните кеширование, используя periodEviction()
каждый час или около того.
2) В противном случае, используйте кэшированное значение, когда загрузка базы данных завершена.
Я полагаю, что неправильно понял будущее Java, поскольку не мог ответить на вопрос: "Что происходит, когда все потоки A,B и C вызывают loadMyItems()
в то же время?"
Значит ли это, что без исполнителя, эта реализация все еще не является поточно-ориентированной?
2 ответа
Еще более простой подход - вообще не кэшировать объект, а просто сохранять Future
,
private CompletableFuture<MyDef> defFuture;
public synchronized void periodEviction() {
// evict by triggering the request anew
defFuture = CompletableFuture.supplyAsync(this::loadFromDatabase);
}
public synchronized Optional<MyDef> loadMyItems() {
try {
return Optional.of(this.defFuture.get());
} catch(InterruptedException e) {
log.error(this.getClass(), "Interrupted whilst getting future..");
} catch(ExecutionException e) {
log.error(this.getClass(), "Error when executing callable future");
}
return Optional.empty();
}
С оговоркой, что это будет вызывать запрос к базе данных каждый период выселения, а не по требованию.
Супер простой подход был бы объявить loadMyItems
как synchronized
, Но если у класса есть другие методы, которые myDef
, вы должны были бы объявить эти synchronized
тоже. Иногда это приводит к очень грубой блокировке и снижению производительности.
Если вы ищете самый чистый / быстрый код, вместо объявления periodEviction
как synchronized
объявить myDef
как AtomicReference
:
private final AtomicReference<MyDef> myDef = new AtomicReference<>();
Тогда тело periodEviction
является:
synchronized (myDef) {
myDef.set(null);
}
И тело loadMyItems
является:
synchronized (myDef) {
if (myDef.get() == null) {
// perform initialization steps, ending with:
myDef.set(this.qDefFuture.get());
}
return myDef.get();
}
Если многие темы звонят loadMyItems
в то же время, myDef
будет когда-либо инициализирован только один раз, и все они получат один и тот же объект, возвращенный (если не вызов periodEviction
пробрался посередине).