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

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

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

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

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

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
130
0
279 425
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

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

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

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

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

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

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
Ответ принят как подходящий

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

Есть также ряд хороших книг (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 16.05.2013 01:49

@ user1914692: Не уверен, какие подводные камни существуют при использовании вышеупомянутого подхода - нужно объяснять дальше?

Piskvor left the building 27.02.2014 17:52

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

user1914692 01.03.2014 21:42

Я не понимаю, как это работает. Если поток a ожидает объекта в synchronised(object), как другой поток может пройти через synchronized(object) для вызова object.notifyAll()? В моей программе все просто застряло на блоках synchronozed.

Tomáš Zato - Reinstate Monica 25.04.2014 14:20

@ TomášZato первый поток вызывает object.wait(), эффективно снимая блокировку этого объекта. Когда второй поток «выходит» из своего синхронизированного блока, тогда другие объекты освобождаются от метода wait и повторно получают блокировку в этой точке.

rogerdpack 07.08.2015 20:21

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

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

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) {

}

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

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

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

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

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

Это в значительной степени просто ожидание. Использование конструкций из пакетов Java 5 util.concurrent должно быть подходящим вариантом. stackoverflow.com/questions/289434/… мне кажется лучшим решением на данный момент.

Cem Catikkas 14.11.2008 20:14

Он занят ожиданием, но если это необходимо только в этом конкретном месте и если нет доступа к библиотеке db, что еще вы можете сделать? Ожидание не обязательно - зло

Mario Ortegón 14.11.2008 21:48

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

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

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

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

CountDownLatch latch = new CountDownLatch(1);

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

latch.await();

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

latch.countDown();

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

lethal-guitar 13.08.2013 15:39

Это использование требует, чтобы вы переделали защелку, когда они разрядятся. Чтобы получить использование, подобное ожидаемому событию в Windows, вы должны попробовать BooleanLatch или сбрасываемый CountDownLatch: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks‌ /…stackoverflow.com/questions/6595835/…

phyatt 08.04.2014 03:07

Привет, если я сначала вызову асинхронный метод, который должен запускать событие как таковое: 1) asyncFunc (); 2) latch.await (); Затем я выполняю обратный отсчет в функции обработки события после его получения. Как я могу убедиться, что событие не будет обработано ДО вызова latch.await ()? Я хотел бы предотвратить приоритетное переключение между строками 1 и 2. Спасибо.

NioX5199 25.05.2015 12:52

Чтобы не ждать вечно в случае ошибок, поместите countDown() в блок finally{}

Daniel Alder 15.05.2017 17:38
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.

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

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

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

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

Требование:

  1. To wait execution of next thread until previous finished.
  2. Next thread must not start until previous thread stops, irrespective of time consumption.
  3. It must be simple and easy to use.

Отвечать ::

@See java.util.concurrent.Future.get() doc.

future.get() Waits if necessary for the computation to complete, and then retrieves its result.

Дело сделано!! См. Пример ниже

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, но ваш подход гораздо более гибкий.

Piskvor left the building 18.01.2012 18:11

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

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

Как это нормально для собеседования, но не для кода?

Piskvor left the building 09.05.2016 21:49

Потому что в этом случае идет последовательно один за другим. Лучшим решением может быть использование семафоров, потому что использование CountdownLatches является лучшим ответом, приведенным здесь, поток никогда не переходит в спящий режим, что означает использование циклов ЦП.

Franco 09.05.2016 22:08

Но дело не в том, чтобы «запускать их последовательно». Я отредактирую вопрос, чтобы прояснить это: поток графического интерфейса ждет, пока база данных не будет готова, а затем оба запускают одновременно для остальной части выполнения приложения: поток графического интерфейса отправляет команды потоку БД и считывает результаты. (И снова: какова часть кода, которую можно использовать в собеседовании, но не в реальном коде? Большинство технических интервьюеров, которых я встречал, имели опыт работы в коде и задавали бы тот же вопрос; плюс мне эта штука нужна для реального приложения Я писала тогда, а не для того, чтобы приставать к домашнему заданию)

Piskvor left the building 09.05.2016 22:11

Так. Это проблема производителя-потребителя, использующего семафоры. Я попробую сделать один пример

Franco 09.05.2016 22:23

Я создал проект github.com/francoj22/SemProducerConsumer/blob/master/src/com‌ /…. Работает нормально.

Franco 09.05.2016 23:28

С

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

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

  1. invokeAll из ExecutorService

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

    Executes the given tasks, returning a list of Futures holding their status and results when all complete.

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

    Creates a work-stealing thread pool using all available processors as its target parallelism level.

Множество правильных ответов, но без простого примера. Вот простой и легкий способ использования 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

Другие вопросы по теме