Як змусити потоки Java чекати виходу іншого потоку?


128

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

Однак при запуску мені потрібно переконатися, що спочатку потік програми чекає, поки db-потік буде готовий (зараз визначається опитуванням користувацького методу dbthread.isReady()). Я б не заперечував, якби нитка додатка блокується, поки потік db не буде готовий.

Thread.join() не схоже на рішення - db-потік виходить лише при відключенні програми.

while (!dbthread.isReady()) {} вид роботи, але порожній цикл споживає багато процесорних циклів.

Будь-які інші ідеї? Дякую.

Відповіді:


128

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

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


Ні весь код потоку не може бути добре інтегрований в об'єкти, якщо я не помиляюся. Тому я не думаю, що використання об'єктної синхронізації - це хороший спосіб реалізувати цю роботу, пов'язану з потоком.
користувач1914692

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

1
@Piskvor: Вибачте, що я написав це давно, і я майже забув, що було на думці. Можливо, я просто мав на увазі краще використовувати замок, а не об'єктну синхронізацію, остання є однією спрощеною формою першої.
користувач1914692

Я не розумію, як це працює. Якщо нитка aчекає на об'єкт, то synchronised(object)як інша нитка може пройти synchronized(object)для виклику object.notifyAll()? У моїй програмі все просто застрягло на synchronozedблоках.
Томаш Зато - Відновіть Моніку

@ TomášZato перший потік викликає object.wait()ефективно розблокування блокування на цьому об'єкті. Коли другий потік "виходить" із свого синхронізованого блоку, то інші об'єкти звільняються від waitметоду і знову отримують замок у цій точці.
rogerdpack

140

Використовуйте CountDownLatch із лічильником 1.

CountDownLatch latch = new CountDownLatch(1);

Тепер у нитці програми зробіть-

latch.await();

У потоці db, коли ви закінчите, зробіть -

latch.countDown();

4
Мені дуже подобається це рішення за його простоту, хоча, можливо, важче зрозуміти значення коду з першого погляду.
летальна гітара

3
Це використання вимагає переробити засувку, коли вони звикнуть. Для того, щоб отримати використання схожого на Waitable подія в Windows, ви повинні спробувати BooleanLatch або самовідновлюється CountDownLatch: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks / ... stackoverflow.com/questions / 6595835 /…
фіат

1
Привіт, якщо я вперше викликаю метод асинхронізації, що він повинен викликати подію як таку: 1) asyncFunc (); 2) latch.await (); Потім я підраховую функцію обробки подій, як тільки вона отримана. Як я можу переконатися, що подія не буде оброблена до того, як буде викликано latch.await ()? Я хотів би запобігти викупу між рядками 1 та 2. Дякую.
NioX5199

1
Щоб не чекати вічно у випадку помилок, покладіть countDown()у finally{}блок
Даніель Олдер

23

Вимога ::

  1. Дочекатися виконання наступного потоку до завершення попереднього.
  2. Наступна нитка не повинна починатися, поки попередня нитка не зупиниться, незалежно від витрат часу.
  3. Він повинен бути простим і легким у використанні.

Відповідь:

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

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();
        }
    }
}

Використовуйте такий клас, як цей:

Створіть ThreEvent:

ThreadEvent resultsReady = new ThreadEvent();

У методі це очікування результатів:

resultsReady.await();

І в методі, який створює результати після створення всіх результатів:

resultsReady.signal();

Редагувати:

(Вибачте за редагування цієї публікації, але цей код має дуже поганий стан гонки, і я не маю достатньої репутації для коментарів)

Ви можете використовувати це, лише якщо ви на 100% впевнені, що сигнал () викликається після await (). Це одна з основних причин, чому ви не можете використовувати об'єкт Java, наприклад, Windows Events.

Якщо код працює в такому порядку:

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

тоді нитка 2 буде чекати вічно . Це відбувається тому, що Object.notify () прокидає лише одну з поточно працюючих потоків. Нитка, яка чекає пізніше, не прокидається. Це сильно відрізняється від того, як я очікую, що події працюватимуть, коли подія передається сигналом, поки а) не чекає або б) явно не скидається.

Примітка. Більшість випадків ви повинні використовувати notifyAll (), але це не стосується вищезгаданої проблеми "почекати назавжди".


7

Спробуйте клас CountDownLatch з java.util.concurrentпакета, який забезпечує механізми синхронізації вищого рівня, набагато менше схильних до помилок, ніж будь-який з матеріалів низького рівня.


6

Ви можете зробити це, використовуючи об'єкт обмінника, розділений між двома потоками:

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

Інтерфейс Future з java.lang.concurrentпакета розроблений для забезпечення доступу до результатів, обчислених в іншій потоці.

Погляньте на FutureTask та ExecutorService як готовий спосіб робити подібні речі.

Я настійно рекомендую прочитати Java Concurrency на практиці всім, хто цікавиться паралельністю та багатопоточністю. Він, очевидно, зосереджений на Java, але м'яса для всіх, хто працює на інших мовах, також є багато.


2

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

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

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

Якщо ви можете змінити код бази даних, тоді краще використовувати мьютекс, як пропонується в інших відповідях.


3
Це майже просто зайнято в очікуванні. Використання конструкцій з пакетів util.concurrent Java 5 має бути дорогою. stackoverflow.com/questions/289434/… вважає мене найкращим рішенням на даний момент.
Джем Катікас

Він зайнятий очікуванням, але якщо це потрібно лише в цьому конкретному місці, і якщо немає доступу до бібліотеки db, що ще можна зробити? Зайняте очікування - це не обов'язково зло
Маріо Ортеґон

2

Це стосується всіх мов:

Ви хочете мати модель події / слухача. Ви створюєте слухача, щоб чекати певної події. Подія буде створена (або сигналізована) у вашій робочій нитці. Це заблокує потік, поки сигнал не буде отриманий, а не постійно опитується, щоб побачити, чи виконується умова, як рішення, яке у вас є.

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

Я пропоную вам розібратися в концепціях використання подій та обробників подій, щоб краще зрозуміти цю парадигму, перш ніж реалізувати свій випадок.

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

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

2

Ви можете прочитати з блокуючої черги в одному потоці і записати його в інший потік.


1

З тих пір

  1. join() було виключено
  2. ви вже використовували CountDownLatch та
  3. Future.get () вже запропоновано іншими експертами,

Ви можете розглянути інші альтернативи:

  1. invokeВсі зExecutorService

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

    Виконує задані завдання, повертаючи список Futures з їх статусом та результатами, коли всі вони завершені.

  2. ForkJoinPool або newWorkStealingPool з Executors(з моменту випуску Java 8)

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


-1

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

Ця ідея може застосовуватися ?. Якщо ви використовуєте CountdownLatches або Semaphores, то це ідеально, але якщо ви шукаєте найпростішу відповідь на інтерв'ю, я думаю, це може застосуватись.


1
Як це нормально для інтерв'ю, але не для фактичного коду?
Пісквор вийшов з будівлі

Тому що в цьому випадку працює послідовно, одне чекання іншого. Найкращим рішенням може бути використання Semaphores, оскільки використання CountdownLatches - це найкраща відповідь, наведена тут. Thread ніколи не лягає спати, що означає використання циклів процесора.
Франко

Але справа не в тому, щоб "запустити їх послідовно". Я відредагую питання, щоб зробити це зрозумілішим: потік GUI чекає, поки база даних буде готова, а потім обидва запускаються одночасно для решти виконання програми: Нитка GUI надсилає команди до потоку БД і зчитує результати. (І знову: в чому сенс коду, який можна використовувати в інтерв'ю, але не в реальному коді? Більшість технічних інтерв'юерів, з якими я зустрічався, мали передумови в коді і задавали б те саме питання; плюс мені ця штука потрібна для фактичної програми Я писав у той час, а не для розсилки домашніх завдань)
Пісквор вийшов із будівлі

1
Так. Це проблема споживачів у споживачах із використанням семафорів. Спробую зробити один приклад
Франко

Я створив проект github.com/francoj22/SemProducerConsumer/blob/master/src/com/… . Це чудово працює.
Франко
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.