Как заставить поток Java ожидать вывода другого потока? - PullRequest
121 голосов
/ 14 ноября 2008

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

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

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

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

Есть еще идеи? Спасибо.

Ответы [ 13 ]

132 голосов
/ 14 ноября 2008

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

CountDownLatch latch = new CountDownLatch(1);

Теперь в ветке приложения сделайте -

latch.await();

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

latch.countDown();
126 голосов
/ 14 ноября 2008

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

Существует также ряд хороших книг (Google для "Параллельное программирование в Java", "Параллелизм Java на практике".

Чтобы получить ответ:

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

22 голосов
/ 18 января 2012

Требование ::

  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.

8 голосов
/ 11 июня 2009
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 resultsReady = new ThreadEvent();

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

resultsReady.await();

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

resultsReady.signal();

EDIT:

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

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

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

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

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

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

7 голосов
/ 14 ноября 2008

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

6 голосов
/ 20 января 2017

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

Вы можете сделать это, используя объект 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) {

}

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

4 голосов
/ 14 ноября 2008

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

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

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

2 голосов
/ 04 мая 2013

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

2 голосов
/ 14 ноября 2008

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

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

Едва ли это что-то, что вы могли бы назвать элегантным кодом, но с этим покончено.

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

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