Java - Может ли CountDownLatch.await () быть переупорядочен компилятором - PullRequest
0 голосов
/ 29 октября 2019

Я должен вызвать операцию в другой системе. Другая система очень параллельна и распределена. Поэтому я интегрирую его через MessageBus. Одна реализация должна позволить вызывающей стороне подождать, пока не будет получен результат, полученный на шине, или истечет время ожидания. Реализация состоит из трех этапов: сначала мне нужно упаковать будущее и CountDownLatch, который высвобождается после получения результата, в ConcurrentHashMap. Эта карта используется совместно с компонентом, который прослушивает MessageBus. Затем я должен начать выполнение и, наконец, дождаться защелки.

public FailSafeFuture execute(Execution execution,long timeout,TimeUnit timeoutUnit) {
    //Step 1
    final WaitingFailSafeFuture future = new WaitingFailSafeFuture();
    final CountDownLatch countDownLatch = new CountDownLatch(1);
    final PendingExecution pendingExecution = new PendingExecution(future, countDownLatch);
    final String id = execution.getId();
    pendingExecutions.put(id, pendingExecution); //ConcurrentHashMap shared with Bus

    //Step 2
    execution.execute();

    //Step 3
    final boolean awaitSuccessfull = countDownLatch.await(timeout, timeoutUnit);

    //...

    return future;
}

Итак, вопрос: может ли эти 3 шага быть переупорядочены компилятором? Насколько я понимаю, только Шаг 1 и Шаг 3 образуют отношения до и после. Таким образом, в теории шаг 2 может быть свободно перемещен компилятором. Может ли шаг 2 даже быть перенесен за ожидание и, следовательно, лишит законной силы код?

Дополнительный вопрос: будет ли замена CountDownLatch комбинацией ожидания / уведомления на общем объекте решить проблему без дальнейшей синхронизации (исключаятайм-аут конечно)

1 Ответ

2 голосов
/ 29 октября 2019

Переупорядочение инструкций может произойти, если конечный результат остается тем же (насколько думает компилятор), и может потребовать дополнительной синхронизации, если задействованы несколько потоков (так как компилятор не знает, что другие потоки могут ожидать определенного порядка).

В одном потоке, как в вашем примере, все шаги имеют отношение happens-before друг к другу. Даже если , если компилятор может переупорядочить эти вызовы методов, конечный результат должен быть таким же (execute(), вызываемый до await()). * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * [*] * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * T T T T T T T M * * * * * * * *} * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *. и await() гарантирует, что код PendingExecution произойдет до выполнения кода await(). Так что в показанном коде нет ничего плохого.

Вы должны всегда отдавать предпочтение java.util.concurrent классам, а не wait/notify, поскольку они проще в использовании и предоставляют гораздо больше функциональности.

...