Come far aspettare un thread Java per l'output di un altro thread?


128

Sto realizzando un'applicazione Java con un thread logico dell'applicazione e un thread di accesso al database. Entrambi persistono per l'intera vita dell'applicazione ed entrambi devono essere eseguiti contemporaneamente (uno parla al server, uno parla all'utente; quando l'app è completamente avviata, ho bisogno che entrambi funzionino).

Tuttavia, all'avvio, devo assicurarmi che inizialmente il thread dell'app attenda che il thread db sia pronto (attualmente determinato eseguendo il polling di un metodo personalizzato dbthread.isReady()). Non mi dispiacerebbe se il thread dell'app si blocca fino a quando il thread db non è pronto.

Thread.join() non sembra una soluzione - il thread db esce solo allo spegnimento dell'app.

while (!dbthread.isReady()) {} funziona, ma il ciclo vuoto consuma molti cicli del processore.

Altre idee? Grazie.

Risposte:


128

Consiglio vivamente di seguire un tutorial come la concorrenza Java di Sun prima di iniziare nel magico mondo del multithreading.

Ci sono anche molti buoni libri (google per "Concurrent Programming in Java", "Java Concurrency in Practice".

Per arrivare alla tua risposta:

Nel tuo codice che deve attendere il dbThread, devi avere qualcosa del genere:

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

Nel tuo dbThreadmetodo, dovresti fare qualcosa del genere:

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

Quello objectYouNeedToLockOnche sto usando in questi esempi è preferibilmente l'oggetto che devi manipolare contemporaneamente da ogni thread, oppure potresti crearne uno separato Objectper quello scopo (non consiglierei di sincronizzare i metodi stessi):

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

Per migliorare la tua comprensione:
ci sono altri (a volte migliori) modi per fare quanto sopra, ad esempio con CountdownLatches, ecc. Da Java 5 ci sono molte classi di concorrenza intelligenti nel java.util.concurrentpacchetto e nei sotto-pacchetti. Hai davvero bisogno di trovare materiale online per conoscere la concorrenza o ottenere un buon libro.


Né tutto il codice thread può essere ben integrato negli oggetti, se non sbaglio. Quindi non penso che usare la sincronizzazione degli oggetti sia un buon modo per implementare questo lavoro relativo al thread.
user1914692,

@ user1914692: Non sei sicuro di quali insidie ​​ci siano nell'utilizzare l'approccio sopra - ti preoccupi di spiegare ulteriormente?
Piskvor lasciò l'edificio il

1
@Piskvor: Mi dispiace di averlo scritto tanto tempo fa e ho quasi dimenticato cosa avevo in mente. Forse intendevo semplicemente usare il blocco, piuttosto che la sincronizzazione degli oggetti, essendo quest'ultima una forma semplificata della prima.
user1914692

Non capisco come funzioni. Se il thread aè in attesa di un oggetto, synchronised(object)come può passare un altro thread synchronized(object)a chiamare il object.notifyAll()? Nel mio programma, tutto è rimasto bloccato sui synchronozedblocchi.
Tomáš Zato - Ripristina Monica il

@ TomášZato il primo thread chiama object.wait()sbloccando efficacemente il blocco su quell'oggetto. Quando il secondo thread "esce" dal suo blocco sincronizzato, gli altri oggetti vengono rilasciati dal waitmetodo e ottengono nuovamente il blocco in quel punto.
rogerdpack,

140

Utilizzare un CountDownLatch con un contatore di 1.

CountDownLatch latch = new CountDownLatch(1);

Ora nel thread dell'app fare-

latch.await();

Nel thread db, dopo aver finito, fai -

latch.countDown();

4
Mi piace molto quella soluzione per la sua semplicità, anche se potrebbe essere più difficile cogliere il significato del codice a prima vista.
chitarra letale

3
Questo utilizzo richiede di rifare il fermo quando si esauriscono. Per ottenere l'uso simile ad un evento waitable in Windows, si dovrebbe provare un BooleanLatch, o un CountDownLatch azzerabile: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/... stackoverflow.com/questions / 6595835 /…
phyatt

1
Ciao, se prima chiamo un metodo asincrono che dovrebbe generare un evento come tale: 1) asyncFunc (); 2) latch.await (); Quindi conto alla rovescia nella funzione di gestione degli eventi una volta ricevuta. Come posso assicurarmi che l'evento non verrà gestito PRIMA di chiamare latch.await ()? Vorrei impedire la prelazione tra la linea 1 e 2. Grazie.
NioX5199,

1
Per evitare di aspettare per sempre in caso di errori, metti countDown()un finally{}blocco
Daniel Alder,

23

Requisiti ::

  1. Attendere l'esecuzione del thread successivo fino al completamento del precedente.
  2. Il thread successivo non deve iniziare fino al termine del thread precedente, indipendentemente dal tempo impiegato.
  3. Deve essere semplice e facile da usare.

Risposta ::

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

future.get () Attende, se necessario, il completamento del calcolo, quindi recupera il risultato.

Lavoro fatto!! Vedi esempio sotto

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

Uscita di questo programma ::

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

Puoi vedere che richiede 6 secondi prima di terminare il suo compito che è maggiore di altri thread. Quindi Future.get () attende il completamento dell'attività.

Se non usi future.get () non aspetta di finire ed esegue il consumo di tempo in base.

Buona fortuna con la concorrenza Java.


La ringrazio per la risposta! Ho usato CountdownLatches, ma il tuo è un approccio molto più flessibile.
Piskvor lasciò l'edificio il

9

Molte risposte corrette ma senza un semplice esempio .. Ecco un modo semplice e semplice come usare 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();
        }
    }
}

Usa questa classe così:

Crea un ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

Nel metodo questo è in attesa di risultati:

resultsReady.await();

E nel metodo che sta creando i risultati dopo che tutti i risultati sono stati creati:

resultsReady.signal();

MODIFICARE:

(Mi dispiace per aver modificato questo post, ma questo codice ha una pessima condizione di gara e non ho abbastanza reputazione per commentare)

Puoi usarlo solo se sei sicuro al 100% che signal () viene chiamato dopo await (). Questa è l'unica grande ragione per cui non è possibile utilizzare oggetti Java come ad esempio Windows Events.

Se il codice viene eseguito in questo ordine:

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

quindi il thread 2 attenderà per sempre . Questo perché Object.notify () riattiva solo uno dei thread attualmente in esecuzione. Un thread in attesa dopo non viene risvegliato. Questo è molto diverso da come mi aspetto che gli eventi funzionino, in cui un evento viene segnalato fino a quando a) atteso o b) viene reimpostato esplicitamente.

Nota: la maggior parte delle volte, è necessario utilizzare NotifyAll (), ma questo non è rilevante per il problema "attendere per sempre" sopra.


7

Prova la classe CountDownLatch fuori dal java.util.concurrentpacchetto, che fornisce meccanismi di sincronizzazione di livello superiore, che sono molto meno soggetti a errori rispetto a qualsiasi altra roba di basso livello.


6

Potresti farlo usando un oggetto Exchanger condiviso tra i due thread:

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

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

E nel secondo thread:

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

}

Come altri hanno già detto, non prendere questo codice spensierato e semplicemente copia-incolla. Prima leggi un po '.


4

L' interfaccia del futuro daljava.lang.concurrent pacchetto è progettata per fornire l'accesso ai risultati calcolati in un altro thread.

Dai un'occhiata a FutureTask ed ExecutorService per un modo già pronto di fare questo tipo di cose.

Consiglio vivamente di leggere Java Concurrency In Practice a chiunque sia interessato alla concorrenza e al multithreading. Si concentra ovviamente su Java, ma c'è molta carne per chiunque lavori anche in altre lingue.


2

Se vuoi qualcosa di veloce e sporco, puoi semplicemente aggiungere una chiamata Thread.sleep () nel tuo ciclo while. Se la libreria del database è qualcosa che non puoi cambiare, allora non c'è davvero altra soluzione facile. Il polling del database fino a quando non è pronto con un periodo di attesa non ucciderà le prestazioni.

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

Difficilmente qualcosa che potresti chiamare codice elegante, ma fa il lavoro.

Nel caso in cui sia possibile modificare il codice del database, è meglio usare un mutex come proposto in altre risposte.


3
Questo è praticamente solo occupato ad aspettare. L'uso dei costrutti dai pacchetti util.concurrent di Java 5 dovrebbe essere la strada da percorrere. stackoverflow.com/questions/289434/… mi sembra la soluzione migliore per ora.
Cem Catikkas,

È occupato ad aspettare, ma se è necessario solo in questo particolare posto, e se non c'è accesso alla libreria db, cos'altro puoi fare? L'attesa non è necessariamente malvagia
Mario Ortegón,

2

Questo vale per tutte le lingue:

Vuoi avere un modello di evento / ascoltatore. Si crea un ascoltatore in attesa di un evento particolare. L'evento verrebbe creato (o segnalato) nel thread di lavoro. Questo bloccherà il thread fino a quando non viene ricevuto il segnale invece del polling costante per vedere se una condizione è soddisfatta, come la soluzione che hai attualmente.

La tua situazione è una delle cause più comuni di deadlock: assicurati di segnalare l'altro thread indipendentemente dagli errori che possono essersi verificati. Esempio: se l'applicazione genera un'eccezione e non chiama mai il metodo per segnalare all'altro che le cose sono state completate. Questo lo farà in modo che l'altro thread non si "svegli" mai.

Ti suggerisco di esaminare i concetti di utilizzo di eventi e gestori di eventi per comprendere meglio questo paradigma prima di implementare il tuo caso.

In alternativa puoi usare una chiamata di funzione di blocco usando un mutex, che farà sì che il thread attenda che la risorsa sia libera. Per fare ciò è necessaria una buona sincronizzazione dei thread, come ad esempio:

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

È possibile leggere da una coda di blocco in un thread e scriverlo in un altro thread.


1

Da

  1. join() è stato escluso
  2. hai già utilizzato CountDownLatch e
  3. Future.get () è già stato proposto da altri esperti,

Puoi considerare altre alternative:

  1. invokeAll fromExecutorService

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

    Esegue le attività assegnate, restituendo un elenco di Futures che mantengono il loro stato e i risultati una volta completati.

  2. ForkJoinPool o newWorkStealingPool da Executors(dalla versione Java 8)

    Crea un pool di thread che ruba il lavoro utilizzando tutti i processori disponibili come livello di parallelismo di destinazione.


-1

inserisci qui la descrizione dell'immagine

Questa idea può essere applicata? Se usi CountdownLatches o Semaphores funziona perfettamente, ma se stai cercando la risposta più semplice per un'intervista, penso che ciò possa valere.


1
Come va bene per un'intervista ma non per il codice reale?
Piskvor lasciò l'edificio

Perché in questo caso è in esecuzione in sequenza uno aspetta un altro. La soluzione migliore potrebbe essere l'utilizzo dei semafori perché l'utilizzo di CountdownLatches è la risposta migliore fornita qui. Il thread non va mai in stop, il che significa l'uso di cicli della CPU.
Franco,

Ma il punto non era "eseguirli in sequenza". Modificherò la domanda per renderlo più chiaro: il thread della GUI attende fino a quando il database non è pronto, quindi entrambi vengono eseguiti contemporaneamente per il resto dell'esecuzione dell'app: il thread della GUI invia comandi al thread del DB e legge i risultati. (E ancora: qual è il punto di codice che può essere utilizzato in un'intervista ma non nel codice reale? La maggior parte degli intervistatori tecnici che ho incontrato aveva un background nel codice e poneva la stessa domanda; inoltre avevo bisogno di questa cosa per un'app reale All'epoca stavo scrivendo, non per fare i compiti a casa)
Piskvor lasciò l'edificio

1
Così. Questo è un problema del consumatore produttore che utilizza i semafori. Proverò a fare un esempio
Franco,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.