Как заставить поток Java ждать вывода другого потока?

128

Я создаю приложение Java с потоком логики приложения и потоком доступа к базе данных. Оба они сохраняются в течение всего срока службы приложения, и оба должны работать одновременно (один разговаривает с сервером, другой разговаривает с пользователем; когда приложение полностью запущено, мне нужно оба работали).

Однако при запуске мне нужно убедиться, что изначально поток приложения ждет, пока поток db не будет готов (в настоящее время определяется путем опроса настраиваемого метода dbthread.isReady()). Я бы не возражал, если поток приложения блокируется до тех пор, пока поток db не будет готов.

Thread.join() не похоже на решение - поток db завершается только при завершении работы приложения.

while (!dbthread.isReady()) {} вроде работает, но пустой цикл потребляет много циклов процессора.

Есть другие идеи? Спасибо.

Писквор вышел из здания
источник

Ответы:

128

Я действительно рекомендую вам пройти обучение, такое как Sun Java Concurrency, прежде чем вы начнете окунуться в волшебный мир многопоточности.

Есть также ряд хороших книг (Google для «Параллельное программирование на Java», «Java Concurrency in Practice».

Чтобы найти ответ:

В вашем коде, который должен ждать dbThread, у вас должно быть что-то вроде этого:

//do some work
synchronized(objectYouNeedToLockOn){
    while (!dbThread.isReady()){
        objectYouNeedToLockOn.wait();
    }
}
//continue with work after dbThread is ready

В вашем dbThreadметоде вам нужно будет сделать что-то вроде этого:

//do db work
synchronized(objectYouNeedToLockOn){
    //set ready flag to true (so isReady returns true)
    ready = true;
    objectYouNeedToLockOn.notifyAll();
}
//end thread run method here

objectYouNeedToLockOnЯ использую в этих примерах предпочтительно объект , который нужно манипулировать одновременно с каждого потока, или вы можете создать отдельный Objectдля этой цели (я бы не рекомендовал делать методы сами синхронизированные):

private final Object lock = new Object();
//now use lock in your synchronized blocks

Для
лучшего понимания: существуют другие (иногда более эффективные) способы сделать это, например, с помощью CountdownLatchesи т. Д. Начиная с Java 5, в java.util.concurrentпакете и подпакетах имеется множество изящных классов параллелизма . Вам действительно нужно найти в Интернете материал, чтобы познакомиться с параллелизмом или получить хорошую книгу.

Герман Линтвельт
источник
Не весь код потока может быть хорошо интегрирован в объекты, если я не ошибаюсь. Поэтому я не думаю, что использование синхронизации объектов - хороший способ реализовать работу, связанную с потоками.
user1914692
@ user1914692: Не уверен, какие подводные камни существуют при использовании вышеуказанного подхода - не забудьте объяснить дальше?
Писквор вышел из здания
1
@Piskvor: Извините, что написал это давным-давно и почти забыл, что у меня на уме. Возможно, я просто имел в виду, что лучше использовать блокировку, а не синхронизацию объектов, поскольку последняя является одной из упрощенных форм первой.
user1914692 01
Я не понимаю, как это работает. Если поток aожидает объекта, synchronised(object)как может другой поток synchronized(object)выполнить вызов object.notifyAll()? В моей программе все просто застряло на synchronozedблоках.
Томаш Зато - Reinstate Monica
@ TomášZato первый поток вызывает object.wait()эффективную разблокировку блокировки этого объекта. Когда второй поток «выходит» из своего синхронизированного блока, другие объекты освобождаются из waitметода и повторно получают блокировку в этой точке.
rogerdpack
140

Используйте CountDownLatch со счетчиком 1.

CountDownLatch latch = new CountDownLatch(1);

Теперь в ветке приложения:

latch.await();

В потоке db после того, как вы закончите, сделайте -

latch.countDown();
pdeva
источник
4
Мне очень нравится это решение за его простоту, хотя с первого взгляда может быть сложнее понять смысл кода.
смертельная гитара
3
Это использование требует, чтобы вы переделали защелку, когда они разрядятся. Чтобы получить использование, подобное ожидаемому событию в Windows, вы должны попробовать BooleanLatch или сбрасываемый CountDownLatch: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 /…
phyatt 07
1
Привет, если я сначала вызову асинхронный метод, который должен запускать событие как таковое: 1) asyncFunc (); 2) latch.await (); Затем я выполняю обратный отсчет в функции обработки события после его получения. Как я могу убедиться, что событие не будет обработано ДО вызова latch.await ()? Я хотел бы предотвратить приоритетное переключение между строками 1 и 2. Спасибо.
NioX5199
1
Чтобы не ждать вечно в случае ошибок, поставьте countDown()в finally{}блок
Дэниел Алдер
23

Требование:

  1. Чтобы дождаться выполнения следующего потока до завершения предыдущего.
  2. Следующий поток не должен запускаться до остановки предыдущего потока, независимо от затрат времени.
  3. Он должен быть простым и легким в использовании.

Ответ ::

@ См. Документ java.util.concurrent.Future.get ().

future.get () Ожидает, если необходимо, завершения вычисления, а затем получает его результат.

Работа выполнена!! См. Пример ниже

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

public class ThreadTest {

    public void print(String m) {
        System.out.println(m);
    }

    public class One implements Callable<Integer> {

        public Integer call() throws Exception {
            print("One...");
            Thread.sleep(6000);
            print("One!!");
            return 100;
        }
    }

    public class Two implements Callable<String> {

        public String call() throws Exception {
            print("Two...");
            Thread.sleep(1000);
            print("Two!!");
            return "Done";
        }
    }

    public class Three implements Callable<Boolean> {

        public Boolean call() throws Exception {
            print("Three...");
            Thread.sleep(2000);
            print("Three!!");
            return true;
        }
    }

    /**
     * @See java.util.concurrent.Future.get() doc
     *      <p>
     *      Waits if necessary for the computation to complete, and then
     *      retrieves its result.
     */
    @Test
    public void poolRun() throws InterruptedException, ExecutionException {
        int n = 3;
        // Build a fixed number of thread pool
        ExecutorService pool = Executors.newFixedThreadPool(n);
        // Wait until One finishes it's task.
        pool.submit(new One()).get();
        // Wait until Two finishes it's task.
        pool.submit(new Two()).get();
        // Wait until Three finishes it's task.
        pool.submit(new Three()).get();
        pool.shutdown();
    }
}

Вывод этой программы:

One...
One!!
Two...
Two!!
Three...
Three!!

Вы можете видеть, что до завершения задачи требуется 6 секунд, что больше, чем у другого потока. Итак, Future.get () ждет, пока задача не будет выполнена.

Если вы не используете future.get (), он не дожидается завершения и выполняет в зависимости от потребления времени.

Удачи с параллелизмом Java.

ясень
источник
Спасибо за ваш ответ! Я использовал CountdownLatches, но ваш подход гораздо более гибкий.
Писквор покинул здание
9

Множество правильных ответов, но без простого примера. Вот простой и простой способ использования CountDownLatch:

//inside your currentThread.. lets call it Thread_Main
//1
final CountDownLatch latch = new CountDownLatch(1);

//2
// launch thread#2
new Thread(new Runnable() {
    @Override
    public void run() {
        //4
        //do your logic here in thread#2

        //then release the lock
        //5
        latch.countDown();
    }
}).start();

try {
    //3 this method will block the thread of latch untill its released later from thread#2
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//6
// You reach here after  latch.countDown() is called from thread#2
Махер Абутраа
источник
8
public class ThreadEvent {

    private final Object lock = new Object();

    public void signal() {
        synchronized (lock) {
            lock.notify();
        }
    }

    public void await() throws InterruptedException {
        synchronized (lock) {
            lock.wait();
        }
    }
}

Тогда используйте этот класс следующим образом:

Создайте ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

В методе ждем результатов:

resultsReady.await();

И в методе, который создает результаты после того, как были созданы все результаты:

resultsReady.signal();

РЕДАКТИРОВАТЬ:

(Извините за редактирование этого сообщения, но у этого кода очень плохое состояние гонки, и у меня недостаточно репутации, чтобы комментировать)

Вы можете использовать это только если вы на 100% уверены, что signal () вызывается после await (). Это одна из основных причин, по которой вы не можете использовать объект Java, например, Windows Events.

Если код выполняется в таком порядке:

Thread 1: resultsReady.signal();
Thread 2: resultsReady.await();

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

Примечание. В большинстве случаев вам следует использовать notifyAll (), но это не имеет отношения к описанной выше проблеме «ждать вечно».

Kosta
источник
7

Попробуйте исключить класс CountDownLatch из java.util.concurrentпакета, который обеспечивает механизмы синхронизации более высокого уровня, которые гораздо менее подвержены ошибкам, чем любой другой материал низкого уровня.

WMR
источник
6

Вы можете сделать это, используя объект Exchanger, совместно используемый двумя потоками:

private Exchanger<String> myDataExchanger = new Exchanger<String>();

// Wait for thread's output
String data;
try {
  data = myDataExchanger.exchange("");
} catch (InterruptedException e1) {
  // Handle Exceptions
}

И во второй ветке:

try {
    myDataExchanger.exchange(data)
} catch (InterruptedException e) {

}

Как говорили другие, не принимайте этот беззаботный и просто скопируйте код. Сначала почитайте.

kgiannakakis
источник
4

Интерфейс будущего отjava.lang.concurrent пакета предназначен для обеспечения доступа к результатам, вычисленным в другом потоке.

Взгляните на FutureTask и ExecutorService, чтобы найти готовый способ делать такие вещи.

Я настоятельно рекомендую прочитать Java Concurrency In Practice всем, кто интересуется параллелизмом и многопоточностью. Очевидно, что он сконцентрирован на Java, но есть много мяса и для тех, кто работает с другими языками.

Билл Мичелл
источник
2

Если вам нужно что-то быстрое и грязное, вы можете просто добавить вызов Thread.sleep () в свой цикл while. Если вы не можете изменить библиотеку базы данных, то другого простого решения действительно нет. Опрос базы данных до готовности с периодом ожидания не убьет производительность.

while (!dbthread.isReady()) {
  Thread.sleep(250);
}

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

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

Марио Ортегон
источник
3
Это в значительной степени просто ожидание. Использование конструкций из пакетов Java 5 util.concurrent должно быть подходящим вариантом. stackoverflow.com/questions/289434/… кажется мне лучшим решением на данный момент.
Cem Catikkas
Он занят ожиданием, но если это необходимо только в этом конкретном месте и если нет доступа к библиотеке db, что еще вы можете сделать? Ожидание не обязательно
Марио Ортегон
2

Это относится ко всем языкам:

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

Ваша ситуация является одной из наиболее распространенных причин взаимоблокировок - убедитесь, что вы сигнализируете другому потоку, независимо от ошибок, которые могли возникнуть. Пример - если ваше приложение выдает исключение и никогда не вызывает метод, чтобы сигнализировать другому, что все было завершено. Это сделает так, чтобы другой поток никогда не «просыпался».

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

В качестве альтернативы вы можете использовать вызов функции блокировки с помощью мьютекса, что заставит поток ждать, пока ресурс освободится. Для этого вам нужна хорошая синхронизация потоков, например:

Thread-A Locks lock-a
Run thread-B
Thread-B waits for lock-a
Thread-A unlocks lock-a (causing Thread-B to continue)
Thread-A waits for lock-b 
Thread-B completes and unlocks lock-b
Klathzazt
источник
2

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

Инго
источник
1

поскольку

  1. join() был исключен
  2. вы уже используете CountDownLatch и
  3. Future.get () уже предлагается другими экспертами,

Вы можете рассмотреть другие альтернативы:

  1. invokeAll изExecutorService

    invokeAll(Collection<? extends Callable<T>> tasks)

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

  2. ForkJoinPool или newWorkStealingPool из Executors(начиная с версии Java 8)

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

Равиндра бабу
источник
-1

введите описание изображения здесь

Эту идею можно применить ?. Если вы используете CountdownLatches или Semaphores, отлично работает, но если вы ищете самый простой ответ для интервью, я думаю, что это применимо.

Франко
источник
1
Как это нормально для собеседования, но не для кода?
Писквор покинул здание
Потому что в этом случае идет последовательно один за другим. Лучшим решением может быть использование семафоров, потому что использование CountdownLatches является лучшим ответом, приведенным здесь, поток никогда не переходит в спящий режим, что означает использование циклов ЦП.
Франко
Но дело не в том, чтобы «запускать их последовательно». Я отредактирую вопрос, чтобы прояснить это: поток графического интерфейса ожидает, пока база данных будет готова, а затем оба запускаются одновременно для остальной части выполнения приложения: поток графического интерфейса отправляет команды потоку БД и считывает результаты. (И снова: какова часть кода, которую можно использовать на собеседовании, но не в реальном коде? Большинство технических интервьюеров, которых я встречал, имели опыт работы в коде и задавали бы тот же вопрос; плюс мне эта штука нужна для реального приложения. Я тогда писал, а не для того, чтобы приставать к домашнему заданию)
Писквор вышел из дома
1
Так. Это проблема производителя-потребителя, использующего семафоры. Постараюсь сделать один пример
Франко
Я создал проект github.com/francoj22/SemProducerConsumer/blob/master/src/com/… . Работает нормально.
Франко