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()

Значит ли это, что без чего-то вроде исполнителя эта реализация все еще не является потокобезопасной?

Что происходит, так это то, что три потока запускают асинхронный запрос, каждый возвращает свой результат, а последний, который установил значение, будет в кеше в течение следующего часа. Чтобы сделать его потокобезопасным, превратите loadMyItems в synchronized.

daniu 28.06.2018 11:01
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
1
148
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Супер простой подход - объявить 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 каким-то образом не попал в середину).

Ответ принят как подходящий

Еще более простой подход - вообще не кэшировать объект, а просто сохранить 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();
}

С оговоркой, что это будет запускать запрос к базе данных каждый период выселения, а не по требованию.

Другие вопросы по теме