Подтвердить что ты не робот

Есть ли в java функция "block until condition станет true"?

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

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"? Или непрерывный цикл стандартного метода ожидания до изменения значения переменной?

4b9b3361

Ответ 1

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

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

Основная тема:

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.

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

Ответ 2

Ответ 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() не был вызван. Это называется ложным пробуждением, и это разрешено, потому что это облегчает выполнение wait/notify в определенных операционных системах.

Ответ 3

Как и в случае с ответом 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 по внутренней синхронизации имеет множество преимуществ, но я просто предпочитаю иметь явный объект Condition для представления условия (у вас может быть более одного, что является приятным прикосновением к вещам, таким как производитель-потребитель).

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

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

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

Ответ 4

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

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

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

    public void reportDetonatingEvent(){
        countDownLatch.countDown();
    }
}

Ответ 5

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

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

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

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

Ответ 6

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

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

Проблема: у меня есть не более одного потока, потребляющего из очереди. Несколько потоков производителей постоянно вставляются в очередь и должны сообщать об этом потребителю, если он ждет. Очередь блокируется, поэтому использование блокировок для уведомления вызывает ненужную блокировку в потоках производителей. Каждый поток производителей должен получить блокировку, прежде чем он сможет уведомить ожидающего пользователя. Я полагаю, что я придумал решение без блокировки, используя 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);
    }
}

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

SignalBarrier barrier = new SignalBarrier();

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

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

Проигрыватель (ы):

doSomethingThatMakesConditionTrue();
barrier.signal();