Могу ли я заставить потоки ждать блокировки, а затем перепрыгивать через нее после освобождения? - PullRequest
0 голосов
/ 01 октября 2019

Дан следующий псевдокод. Функция может быть введена несколькими потоками одновременно. Я хочу, чтобы все потоки выполнялись a() и c(), но b() должны выполняться только теми потоками, которые не были заблокированы при входе в синхронизированный блок.

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

    public void code() {
        a() // check if page is present locally

        if (pageMissing) {
            synchronized (this) {
                page = b(); // get page over REST
            }
        }

        c() // access page
    }

Чем это полезно? Представьте, что b() вызывает внешнюю функцию REST для обновления локальных данных. Когда поток входит, мы хотим быть уверены, что функция вызывается и локальные данные обновляются, но как только блокирующий поток завершает работу b(), мы знаем, что локальные данные являются текущими, и мы не хотим тратить ресурсы на последовательные потоки, которыеуже ждали вызова функции обновления снова.

1 Ответ

0 голосов
/ 01 октября 2019

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

private boolean isDone = false;

public void code() {
    a();
    synchronized (this) {
        if (!isDone) {
            b();
            isDone = true;
        }
    }
    c();
}

Только первый поток, получивший блокировку, будет вызывать b(). После успешного завершения он установит для флага значение true и выйдет из блока. Все последующие потоки, входящие в блок, потерпят неудачу при условии , если , и, следовательно, не будут вызывать b().


Если вы не хотите, чтобы другие потоки ожидали синхронизированного блоказаблокировать и хотите продолжить вызывать c(), вы можете удалить синхронизированное ключевое слово и использовать AtomicBoolean и использовать его compareAndSet

 private AtomicBoolean isDone = new AtomicBoolean(false);

 public void code() {
    a();
    if (isDone.compareAndSet(false, true)) {
       b();
    }
    c();
}

Javadoc из compareAndSet состояний

Атомно устанавливает значение для данного обновленного значения, если текущее значение {@code ==} ожидаемое значение.

@ return {@code true} в случае успеха. Ложный возврат означает, что фактическое значение не было равно ожидаемому.

Итак, первый поток, который вызывает compareAndSet, в то время как isDone имеет значение false, будет успешным. Остальные получат false в качестве возвращаемого значения и, следовательно, не войдут в блок и могут продолжить.


ОБНОВЛЕНИЕ: Из ваших комментариев

@ user7 Да, я обеспокоен тем, что потоки без необходимости вызывают b (), но (см. Мой другой комментарий) мне нужны те потоки, которые приходят, пока b () работает, чтобы дождаться его завершения, прежде чем они продолжат выполнение c ()

Кажется, вам нужен мой первый раствор.

...