есть ли в блоке «блокировка до тех пор, пока условие не станет истинным»? - PullRequest
61 голосов
/ 14 мая 2011

Я пишу поток слушателя для сервера, и в настоящее время я использую:

while (true){
    try {
        if (condition){
            //do something
            condition=false;
        }
        sleep(1000);

    } catch (InterruptedException ex){
        Logger.getLogger(server.class.getName()).log(Level.SEVERE, null, ex);
    }
}

С кодом выше, я сталкиваюсь с проблемами с функцией запуска, съедая всевремя цикла.Функция сна работает, но кажется, что это временное исправление, а не решение.

Есть ли какая-нибудь функция, которая блокировала бы, пока переменная 'condition' не стала 'true'?Или непрерывный цикл - стандартный метод ожидания изменения значения переменной?

Ответы [ 6 ]

54 голосов
/ 14 мая 2011

Такой опрос, безусловно, является наименее предпочтительным решением.

Я предполагаю, что у вас есть другой поток, который сделает что-то, чтобы выполнить условие. Есть несколько способов синхронизации потоков. Самым простым в вашем случае будет уведомление через объект:

Основная нить:

synchronized(syncObject) {
    try {
        // Calling wait() will block this thread until another thread
        // calls notify() on the object.
        syncObject.wait();
    } catch (InterruptedException e) {
        // Happens if someone interrupts your thread.
    }
}

Другая тема:

// Do something
// If the condition is true, do the following:
synchronized(syncObject) {
    syncObject.notify();
}

syncObject сам по себе может быть простым Object.

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

40 голосов
/ 06 октября 2014

Ответ EboMike и Ответ Тоби оба находятся на правильном пути, но оба они содержат роковой недостаток.Ошибка называется уведомление о потере .

Проблема в том, что если поток вызывает foo.notify(), он вообще ничего не будет делать, если какой-то другой поток уже не спит в foo.wait()вызов.Объект foo не помнит, что он был уведомлен.

Существует причина, по которой вам не разрешено вызывать foo.wait() или foo.notify(), если поток не синхронизирован с foo.Это потому, что единственный способ избежать потерянных уведомлений - защитить условие с помощью мьютекса.Когда все сделано правильно, это выглядит так:

Поток потребителя:

try {
    synchronized(foo) {
        while(! conditionIsTrue()) {
            foo.wait();
        }
        doSomethingThatRequiresConditionToBeTrue();
    }
} catch (InterruptedException e) {
    handleInterruption();
}

Поток производителя:

synchronized(foo) {
    doSomethingThatMakesConditionTrue();
    foo.notify();
}

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

Также обратите внимание, что wait() находится в цикле.Это связано с тем, что в общем случае к тому времени, когда потребитель повторно получает блокировку foo и просыпается, какой-то другой поток мог снова сделать условие ложным.Даже если это невозможно в вашей программе, в некоторых операционных системах возможно, что foo.wait() вернется, даже если foo.notify() не был вызван.Это называется ложным пробуждением , и это допускается, потому что облегчает реализацию ожидания / уведомления в определенных операционных системах.

18 голосов
/ 17 мая 2011

Аналогично ответу EboMike, вы можете использовать механизм, аналогичный wait / notify / notifyAll, но настроенный для использования Lock.

Например,

public void doSomething() throws InterruptedException {
    lock.lock();
    try {
        condition.await(); // releases lock and waits until doSomethingElse is called
    } finally {
        lock.unlock();
    }
}

public void doSomethingElse() {
    lock.lock();
    try {
        condition.signal();
    } finally {
        lock.unlock();
    }
}

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

Использование Lock s по сравнению с внутренней синхронизацией имеет много преимуществ, но я просто предпочитаю иметь явный объект Condition для представления условия (у вас может быть более одного, что приятно для таких вещей, как производитель-потребитель).

Кроме того, я не могу не заметить, как вы справляетесь с прерванным исключением в вашем примере. Вы, вероятно, не должны использовать исключение, подобное этому, вместо этого сбросьте флаг состояния прерывания, используя Thread.currentThrad().interrupt.

Это потому, что если выдается исключение, флаг состояния прерывания будет сброшен (он говорит: " Я больше не помню, чтобы меня прерывали, я не смогу никому сказать, что был, если они спросят * 1018" * ") и другой процесс может опираться на этот вопрос. Примером является то, что кто-то еще реализовал политику прерывания на основе этого ... фу. Еще одним примером может быть то, что вы используете политику прерывания, а не то, что while(true) мог бы быть реализован как while(!Thread.currentThread().isInterrupted() (что также сделает ваш код более ... социально продуманным).

Таким образом, в итоге, использование Condition грубо эквивалентно использованию wait / notify / notifyAll, когда вы хотите использовать Lock, ведение журнала - зло и глотание InterruptedException - шалость;)

15 голосов
/ 02 октября 2017

Поскольку никто не опубликовал решение с CountDownLatch.Как насчет:

public class Lockeable {
    private final CountDownLatch countDownLatch = new CountDownLatch(1);

    public void doAfterEvent(){
        countDownLatch.await();
        doSomething();
    }

    public void reportDetonatingEvent(){
        countDownLatch.countDown();
    }
}
5 голосов
/ 14 мая 2011

Вы можете использовать семафор .

Пока условие не выполняется, другой поток получает семафор.
Ваш поток попытается получить его с помощью acquireUninterruptibly()
или tryAcquire(int permits, long timeout, TimeUnit unit) и будут заблокированы.

Когда условие выполнено, семафор также освобождается, и ваш поток может его получить.

Вы также можете попробовать использовать SynchronousQueue или CountDownLatch.

4 голосов
/ 17 марта 2015

Безблокировочное решение (?)

У меня была та же проблема, но я хотел решение, которое не использовало бы блокировки.

Проблема: у меня не более одного потока, потребляющего из очереди. Несколько потоков производителей постоянно вставляются в очередь и должны уведомить потребителя, если он ждет. Очередь не блокируется, поэтому использование блокировок для уведомлений приводит к ненужной блокировке в потоках производителя. Каждый поток производителя должен получить блокировку, прежде чем он сможет уведомить ожидающего потребителя. Мне кажется, я нашел решение без блокировки, используя LockSupport и AtomicReferenceFieldUpdater. Если в JDK существует барьер без блокировки, я не смог бы его найти. И CyclicBarrier, и CoundDownLatch используют внутренние блокировки из того, что я смог найти.

Это мой слегка сокращенный код. Просто чтобы быть понятным, этот код будет позволять один поток ждать одновременно. Его можно изменить, чтобы разрешить использование нескольких ожидающих / потребителей, используя некоторый тип атомарной коллекции для хранения нескольких владельцев (может работать ConcurrentMap).

Я использовал этот код, и он работает. Я не проверял это экстенсивно. Я предлагаю вам прочитать документацию для LockSupport перед использованием.

/* I release this code into the public domain.
 * http://unlicense.org/UNLICENSE
 */

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.LockSupport;

/**
 * A simple barrier for awaiting a signal.
 * Only one thread at a time may await the signal.
 */
public class SignalBarrier {
    /**
     * The Thread that is currently awaiting the signal.
     * !!! Don't call this directly !!!
     */
    @SuppressWarnings("unused")
    private volatile Thread _owner;

    /** Used to update the owner atomically */
    private static final AtomicReferenceFieldUpdater<SignalBarrier, Thread> ownerAccess =
        AtomicReferenceFieldUpdater.newUpdater(SignalBarrier.class, Thread.class, "_owner");

    /** Create a new SignalBarrier without an owner. */
    public SignalBarrier() {
        _owner = null;
    }

    /**
     * Signal the owner that the barrier is ready.
     * This has no effect if the SignalBarrer is unowned.
     */
    public void signal() {
        // Remove the current owner of this barrier.
        Thread t = ownerAccess.getAndSet(this, null);

        // If the owner wasn't null, unpark it.
        if (t != null) {
            LockSupport.unpark(t);
        }
    }

    /**
     * Claim the SignalBarrier and block until signaled.
     *
     * @throws IllegalStateException If the SignalBarrier already has an owner.
     * @throws InterruptedException If the thread is interrupted while waiting.
     */
    public void await() throws InterruptedException {
        // Get the thread that would like to await the signal.
        Thread t = Thread.currentThread();

        // If a thread is attempting to await, the current owner should be null.
        if (!ownerAccess.compareAndSet(this, null, t)) {
            throw new IllegalStateException("A second thread tried to acquire a signal barrier that is already owned.");
        }

        // The current thread has taken ownership of this barrier.
        // Park the current thread until the signal. Record this
        // signal barrier as the 'blocker'.
        LockSupport.park(this);
        // If a thread has called #signal() the owner should already be null.
        // However the documentation for LockSupport.unpark makes it clear that
        // threads can wake up for absolutely no reason. Do a compare and set
        // to make sure we don't wipe out a new owner, keeping in mind that only
        // thread should be awaiting at any given moment!
        ownerAccess.compareAndSet(this, t, null);

        // Check to see if we've been unparked because of a thread interrupt.
        if (t.isInterrupted())
            throw new InterruptedException();
    }

    /**
     * Claim the SignalBarrier and block until signaled or the timeout expires.
     *
     * @throws IllegalStateException If the SignalBarrier already has an owner.
     * @throws InterruptedException If the thread is interrupted while waiting.
     *
     * @param timeout The timeout duration in nanoseconds.
     * @return The timeout minus the number of nanoseconds that passed while waiting.
     */
    public long awaitNanos(long timeout) throws InterruptedException {
        if (timeout <= 0)
            return 0;
        // Get the thread that would like to await the signal.
        Thread t = Thread.currentThread();

        // If a thread is attempting to await, the current owner should be null.
        if (!ownerAccess.compareAndSet(this, null, t)) {
            throw new IllegalStateException("A second thread tried to acquire a signal barrier is already owned.");
        }

        // The current thread owns this barrier.
        // Park the current thread until the signal. Record this
        // signal barrier as the 'blocker'.
        // Time the park.
        long start = System.nanoTime();
        LockSupport.parkNanos(this, timeout);
        ownerAccess.compareAndSet(this, t, null);
        long stop = System.nanoTime();

        // Check to see if we've been unparked because of a thread interrupt.
        if (t.isInterrupted())
            throw new InterruptedException();

        // Return the number of nanoseconds left in the timeout after what we
        // just waited.
        return Math.max(timeout - stop + start, 0L);
    }
}

Чтобы дать смутный пример использования, я приведу пример Джеймса большого:

SignalBarrier barrier = new SignalBarrier();

Потребительская нить (единственное число, не множественное число! ):

try {
    while(!conditionIsTrue()) {
        barrier.await();
    }
    doSomethingThatRequiresConditionToBeTrue();
} catch (InterruptedException e) {
    handleInterruption();
}

Нить (и) производителя:

doSomethingThatMakesConditionTrue();
barrier.signal();
...