Ожидание события в Java - насколько это сложно? - PullRequest
15 голосов
/ 19 декабря 2011

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

Thread 1:
    while(true) {
        ...do something...
        foo.notifyAll()
        ...wait for some condition that might never happen...
        ...
    }

Thread 2:
    ...
    foo.wait();
    ...

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

Мои возможные решения:

a) Я мог бы использовать CountDownLatch или Future, но у обоих есть проблема в том, что они по своей сути запускают только один раз .То есть в цикле while потока 1 мне нужно было бы создать новый foo для ожидания каждый раз, а поток 2 должен был бы спросить, какого foo ждать.У меня плохое предчувствие простой записи

while(true) {
   foo = new FutureTask(); 
   ...
   foo.set(...);
   ...wait for a condition that might never be set...
   ...
}

, так как я боюсь, что при foo = new FutureTask (), что происходит, когда кто-то ждал старого foo (по «какой-то причине»), set не вызывался,например ошибка в обработке исключений)?

б) Или я мог бы использовать семафор:

class Event {
   Semaphore sem;
   Event() { sem = new Semaphore(1); sem . }
   void signal() { sem.release(); }
   void reset() { sem.acquire(1); }
   void wait() { if (sem.tryAcquire(1)) { sem.release(); } }
}

Но я боюсь, что есть какое-то состояние гонки, если несколько потоков ждут его, а другой сигнализирует ()s и reset () s.

Вопрос:

Нет ли в API Java ничего похожего на поведение событий Windows?Или, если вы презираете Windows, что-то вроде WaitGroup golang (то есть CountDownLatch, который позволяет countUp ())?Что-нибудь?

Как сделать это вручную:

Поток 2 не может просто ждать из-за ложного пробуждения, и в Java нет способа узнать, почему Object.wait ()вернулся.Поэтому мне нужна переменная условия, которая хранит, сигнализировано ли событие или нет.Поток 2:

synchronized(foo) {
    while(!condition) {
        foo.wait();
    }
}

И Поток 1, конечно, устанавливает условие в true в синхронизированном блоке.Спасибо Weekens за подсказку!

Существует ли существующий класс, который переносит это поведение?

Или мне нужно скопировать и вставить код повсюду?

Ответы [ 4 ]

20 голосов
/ 19 декабря 2011

Обычной практикой является изменение состояния при выполнении notifyAll и проверка состояния при выполнении wait ().

, например

boolean ready = false;

// thread 1
synchronized(lock) {
    ready = true;
    lock.notifyAll();
}


// thread 2
synchronized(lock) {
    while(!ready) 
        lock.wait();
}

При таком подходе не имеет значения, будет ли поток 1 или поток 2 первым получать блокировку.

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

3 голосов
/ 19 декабря 2011

Вы можете использовать wait () с timeout, в этом случае вы не рискуете ждать вечно.Также обратите внимание, что wait () может возвращаться, даже если notify () вообще не было, поэтому вам нужно заключить ожидание в некоторый условный цикл.Это стандартный способ ожидания в Java.

synchronized(syncObject) {
    while(condition.isTrue()) {
        syncObject.wait(WAIT_TIMEOUT);
    }
}

(в вашей ветке 2)

Редактировать: Перемещено синхронизировано вне цикла.

2 голосов
/ 19 декабря 2011

Я бы использовал BlockingQueue между двумя потоками.Использование wait и notify - 5 минут назад;)

enum Event {
  Event,
  Stop;
}

BlockingQueue<Event> queue = new LinkedBlockingQueue<Event>();

// Thread 1
try {
  while(true) {
    ...do something...
    queue.put(Event.Event);
    ...wait for some condition that might never happen...
    ...
  }
} finally {
  // Tell other thread we've finished.
  queue.put(Event.Stop};
}

// Thread 2
...
switch ( queue.take() ) {
  case Event:
       ...
       break;

  default:
       ...
       break;
}
2 голосов
/ 19 декабря 2011

Самый простой способ - просто сказать

firstThread.join();

Это будет блокировка до завершения первого потока.

Но вы можете реализовать то же самое, используя wait/поставить в известность.К сожалению, вы не разместили свои реальные фрагменты кода, но я предполагаю, что если ожидание не завершается, когда вы звоните, уведомите об этом, потому что вы не поместили оба в блок synchronized.Обратите внимание, что «аргумент» блока synchronized должен быть одинаковым для пары ожидания / уведомления.

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