Jak sprawić, by wątek Java czekał na wyjście innego wątku?


128

Tworzę aplikację Java z wątkiem logiki aplikacji i wątkiem dostępu do bazy danych. Oba trwają przez cały okres istnienia aplikacji i oba muszą działać w tym samym czasie (jeden rozmawia z serwerem, jeden z użytkownikiem; gdy aplikacja jest w pełni uruchomiona, potrzebuję ich obu do działania).

Jednak podczas uruchamiania muszę się upewnić, że początkowo wątek aplikacji czeka, aż wątek db będzie gotowy (obecnie określony przez odpytywanie niestandardową metodą dbthread.isReady()). Nie miałbym nic przeciwko, gdyby wątek aplikacji blokował się, dopóki wątek db nie był gotowy.

Thread.join() nie wygląda na rozwiązanie - wątek db jest zamykany tylko podczas zamykania aplikacji.

while (!dbthread.isReady()) {} rodzaj działa, ale pusta pętla pochłania wiele cykli procesora.

Jakieś inne pomysły? Dzięki.

Odpowiedzi:


127

Naprawdę poleciłbym zapoznanie się z samouczkiem, takim jak Współbieżność Java firmy Sun, przed rozpoczęciem magicznego świata wielowątkowości.

Jest też wiele dobrych książek (w Google na temat „Concurrent Programming in Java”, „Java Concurrency in Practice”.

Aby dostać się do odpowiedzi:

W swoim kodzie, który musi czekać na dbThread, musisz mieć coś takiego:

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

W swojej dbThreadmetodzie musiałbyś zrobić coś takiego:

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

objectYouNeedToLockOnUżywam w tych przykładach korzystnie jest obiekt, który trzeba manipulować jednocześnie z każdego wątku, albo można utworzyć oddzielne Objectdo tego celu (nie polecam podejmowania metod same zsynchronizowane):

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

Aby pogłębić zrozumienie:
Istnieją inne (czasem lepsze) sposoby wykonania powyższego, np. Za pomocą CountdownLatchesitp. Od Java 5 istnieje wiele fajnych klas współbieżności w java.util.concurrentpakiecie i pod-pakietach. Naprawdę musisz znaleźć materiał online, aby poznać współbieżność lub zdobyć dobrą książkę.


Jeśli się nie mylę, nie można też ładnie zintegrować kodu wątku z obiektami. Więc nie sądzę, aby używanie synchronizacji obiektów było dobrym sposobem na wdrożenie tej pracy związanej z wątkiem.
user1914692

@ user1914692: Nie masz pewności, jakie są pułapki związane z zastosowaniem powyższego podejścia - czy chcesz to wyjaśnić dalej?
Piskvor opuścił budynek

1
@Piskvor: Przepraszam, że napisałem to dawno temu i prawie zapomniałem, o czym myślę. Może po prostu chciałem lepiej użyć blokady niż synchronizacji obiektów, ponieważ ta ostatnia jest jedną z uproszczonych form tej pierwszej.
user1914692

Nie rozumiem, jak to działa. Jeśli wątek aoczekuje na obiekt, w synchronised(object)jaki sposób inny wątek może przejść, synchronized(object)aby wywołać funkcję object.notifyAll()? W moim programie wszystko po prostu utknęło na synchronozedblokach.
Tomáš Zato - Przywróć Monikę

@ TomášZato pierwsze wywołania wątku object.wait()skutecznie odblokowują blokadę tego obiektu. Kiedy drugi wątek „wychodzi” ze swojego zsynchronizowanego bloku, inne obiekty są zwalniane z waitmetody i ponownie uzyskują blokadę w tym punkcie.
rogerdpack

141

Użyj CountDownLatch z licznikiem 1.

CountDownLatch latch = new CountDownLatch(1);

Teraz w wątku aplikacji wykonaj-

latch.await();

W wątku db, po zakończeniu, wykonaj -

latch.countDown();

4
Naprawdę podoba mi się to rozwiązanie ze względu na jego prostotę, chociaż na pierwszy rzut oka może być trudniej zrozumieć znaczenie kodu.
zabójcza gitara

3
To użycie wymaga przerobienia zatrzasku, gdy się zużyją. Aby uzyskać użycie podobne do Waitable Event w systemie Windows, należy wypróbować BooleanLatch lub resetowalny CountDownLatch: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 /…
phyatt

1
Cześć, jeśli najpierw wywołam metodę asynchroniczną, która ma wywołać takie zdarzenie: 1) asyncFunc (); 2) latch.await (); Następnie odliczam odliczanie w funkcji obsługi zdarzenia po jego otrzymaniu. Jak mogę się upewnić, że zdarzenie nie zostanie obsłużone PRZED wywołaniem funkcji latch.await ()? Chciałbym zapobiec wywłaszczaniu między wierszami 1 i 2. Dziękuję.
NioX5199

1
Aby uniknąć wiecznego czekania na błędy, postaw countDown()na finally{}blok
Daniel Alder

23

Wymagania ::

  1. Czekać na wykonanie następnego wątku do zakończenia poprzedniego.
  2. Następny wątek nie może rozpoczynać się przed zakończeniem poprzedniego wątku, bez względu na czasochłonność.
  3. Musi być proste i łatwe w użyciu.

Odpowiedź ::

@ Zobacz dokument java.util.concurrent.Future.get ().

future.get () Czeka, jeśli to konieczne, na zakończenie obliczeń, a następnie pobiera ich wynik.

Zadanie wykonane!! Zobacz przykład poniżej

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

Wynik tego programu:

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

Jak widać, zajmuje to 6 sekund, zanim zakończy swoje zadanie, które jest dłuższe niż w przypadku innych wątków. Tak więc Future.get () czeka, aż zadanie zostanie wykonane.

Jeśli nie używasz future.get (), nie czeka na zakończenie i wykonuje oparte na zużyciu czasu.

Powodzenia z współbieżnością Java.


Dziękuję za Twoją odpowiedź! Użyłem CountdownLatches, ale twoje podejście jest znacznie bardziej elastyczne.
Piskvor opuścił budynek

10

Wiele poprawnych odpowiedzi, ale bez prostego przykładu. Oto łatwy i prosty sposób użycia 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();
        }
    }
}

Użyj tej klasy w ten sposób:

Utwórz ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

W metodzie to czeka na wyniki:

resultsReady.await();

A w metodzie tworzącej wyniki po utworzeniu wszystkich wyników:

resultsReady.signal();

EDYTOWAĆ:

(Przepraszamy za edycję tego posta, ale ten kod ma bardzo zły stan wyścigu i nie mam wystarczającej reputacji, aby komentować)

Możesz tego użyć tylko wtedy, gdy masz 100% pewności, że signal () jest wywoływane po await (). To jest jeden duży powód, dla którego nie możesz używać obiektu Java, takiego jak np. Zdarzenia Windows.

Jeśli kod działa w tej kolejności:

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

wtedy wątek 2 będzie czekał wiecznie . Dzieje się tak, ponieważ Object.notify () budzi tylko jeden z aktualnie działających wątków. Wątek oczekujący później nie jest budzony. To bardzo różni się od tego, w jaki sposób spodziewam się, że zdarzenia będą działać, gdy zdarzenie jest sygnalizowane do a) czekania na lub b) jawnego resetowania.

Uwaga: w większości przypadków należy używać notifyAll (), ale nie ma to związku z powyższym problemem „czekać wiecznie”.


7

Wypróbuj klasę CountDownLatch z java.util.concurrentpakietu, która zapewnia mechanizmy synchronizacji wyższego poziomu, które są znacznie mniej podatne na błędy niż jakiekolwiek inne rzeczy niskiego poziomu.


6

Możesz to zrobić za pomocą obiektu Exchanger współdzielonego między dwoma wątkami:

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

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

A w drugim wątku:

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

}

Jak powiedzieli inni, nie bierz tego lekkomyślnie i po prostu kopiuj i wklej kod. Najpierw poczytaj.


4

Przyszłości interfejs z java.lang.concurrentopakowania jest zaprojektowana w celu zapewnienia dostępu do wyników obliczonych w innym wątku.

Zapoznaj się z FutureTask i ExecutorService, aby uzyskać gotowy sposób robienia tego typu rzeczy.

Zdecydowanie polecam przeczytanie współbieżności języka Java w praktyce wszystkim zainteresowanym współbieżnością i wielowątkowością. Oczywiście koncentruje się na Javie, ale jest też mnóstwo mięsa dla każdego, kto pracuje w innych językach.


2

Jeśli chcesz czegoś szybkiego i brudnego, możesz po prostu dodać wywołanie Thread.sleep () w pętli while. Jeśli biblioteka bazy danych jest czymś, czego nie można zmienić, tak naprawdę nie ma innego prostego rozwiązania. Sondowanie bazy danych, dopóki nie będzie gotowa z okresem oczekiwania, nie zabije wydajności.

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

Prawie nie jest to coś, co można nazwać eleganckim kodem, ale wykonuje swoją pracę.

W przypadku, gdy możesz zmodyfikować kod bazy danych, lepsze jest użycie muteksu, jak zaproponowano w innych odpowiedziach.


3
To jest po prostu zajęte czekaniem. Użycie konstrukcji z pakietów util.concurrent Javy 5 powinno być drogą do zrobienia. stackoverflow.com/questions/289434/… wydaje mi się na razie najlepszym rozwiązaniem.
Cem Catikkas

Jest zajęty czekaniem, ale jeśli jest to konieczne tylko w tym konkretnym miejscu i jeśli nie ma dostępu do biblioteki db, co jeszcze możesz zrobić? Zajęte oczekiwanie niekoniecznie jest złe
Mario Ortegón

2

Dotyczy to wszystkich języków:

Chcesz mieć model zdarzenia / nasłuchiwania. Tworzysz odbiornik, aby czekać na określone wydarzenie. Zdarzenie zostanie utworzone (lub zasygnalizowane) w twoim wątku roboczym. Spowoduje to zablokowanie wątku do momentu odebrania sygnału, zamiast ciągłego odpytywania w celu sprawdzenia, czy warunek jest spełniony, tak jak w przypadku rozwiązania, które obecnie masz.

Twoja sytuacja jest jedną z najczęstszych przyczyn zakleszczeń - upewnij się, że sygnalizujesz inny wątek, niezależnie od błędów, które mogły wystąpić. Przykład - jeśli twoja aplikacja zgłasza wyjątek - i nigdy nie wywołuje metody, aby zasygnalizować innym, że rzeczy się zakończyły. Dzięki temu drugi wątek nigdy się nie „obudzi”.

Proponuję przyjrzeć się koncepcjom używania zdarzeń i programów obsługi zdarzeń, aby lepiej zrozumieć ten paradygmat przed wdrożeniem sprawy.

Alternatywnie możesz użyć wywołania funkcji blokującej za pomocą muteksu - co spowoduje, że wątek będzie czekał na zwolnienie zasobu. Aby to zrobić, potrzebujesz dobrej synchronizacji wątków - takiej jak:

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

Można było czytać z kolejki blokującej w jednym wątku i pisać do niej w innym wątku.


1

Od

  1. join() został wykluczony
  2. używasz już CountDownLatch i
  3. Future.get () jest już proponowane przez innych ekspertów,

Możesz rozważyć inne alternatywy:

  1. invokeAll fromExecutorService

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

    Wykonuje podane zadania, zwracając listę kontraktów futures posiadających status i wyniki po wykonaniu wszystkich.

  2. ForkJoinPool lub newWorkStealingPool z Executors(od wydania Java 8)

    Tworzy pulę wątków kradnącą pracę przy użyciu wszystkich dostępnych procesorów jako docelowego poziomu równoległości.


-1

wprowadź opis obrazu tutaj

Ten pomysł można zastosować ?. Jeśli używasz CountdownLatches lub Semaphores, działa idealnie, ale jeśli szukasz najłatwiejszej odpowiedzi na rozmowę kwalifikacyjną, myślę, że to może mieć zastosowanie.


1
Jak to jest w porządku w przypadku rozmowy kwalifikacyjnej, ale nie w przypadku rzeczywistego kodu?
Piskvor opuścił budynek

Ponieważ w tym przypadku działa sekwencyjnie jedno oczekiwanie na drugie. Najlepszym rozwiązaniem może być użycie semaforów, ponieważ użycie CountdownLatches jest najlepszą odpowiedzią podaną tutaj, wątek nigdy nie przechodzi w stan uśpienia, co oznacza użycie cykli procesora.
Franco

Ale nie chodziło o to, by „uruchamiać je po kolei”. Zmienię pytanie, aby uczynić to jaśniejszym: Wątek GUI czeka, aż baza danych będzie gotowa, a następnie oba działają jednocześnie przez resztę wykonania aplikacji: wątek GUI wysyła polecenia do wątku DB i odczytuje wyniki. (I znowu: jaki jest sens kodu, którego można użyć w wywiadzie, ale nie w rzeczywistym kodzie? Większość ankieterów technicznych, których spotkałem, miała doświadczenie w kodzie i zadawałaby to samo pytanie; plus potrzebowałem tego do rzeczywistej aplikacji Pisałem wtedy, a nie po to, żeby rozwlekać prace domowe)
Piskvor opuścił budynek

1
Więc. Jest to problem konsumenta producenta używającego semaforów. Spróbuję zrobić jeden przykład
Franco

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.