Comment faire attendre un thread Java pour la sortie d'un autre thread?

128

Je crée une application Java avec un thread d'application logique et un thread d'accès à la base de données. Les deux persistent pendant toute la durée de vie de l'application et doivent tous deux être exécutés en même temps (l'un parle au serveur, l'autre à l'utilisateur; lorsque l'application est complètement démarrée, j'ai besoin des deux pour fonctionner).

Cependant, au démarrage, je dois m'assurer qu'au départ, le thread d'application attend que le thread db soit prêt (actuellement déterminé en interrogeant une méthode personnalisée dbthread.isReady()). Cela ne me dérangerait pas si le thread d'application se bloque jusqu'à ce que le thread db soit prêt.

Thread.join() ne ressemble pas à une solution - le thread db se termine uniquement à l'arrêt de l'application.

while (!dbthread.isReady()) {} fonctionne, mais la boucle vide consomme beaucoup de cycles de processeur.

D'autres idées? Merci.

Piskvor a quitté le bâtiment
la source

Réponses:

128

Je vous recommande vraiment de suivre un didacticiel comme la concurrence Java de Sun avant de vous lancer dans le monde magique du multithreading.

Il existe également un certain nombre de bons livres (google pour «Concurrent Programming in Java», «Java Concurrency in Practice»).

Pour obtenir votre réponse:

Dans votre code qui doit attendre le dbThread, vous devez avoir quelque chose comme ceci:

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

Dans votre dbThreadméthode, vous devrez faire quelque chose comme ceci:

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

Le que objectYouNeedToLockOnj'utilise dans ces exemples est de préférence l'objet que vous devez manipuler simultanément à partir de chaque thread, ou vous pouvez en créer un séparé Objectà cette fin (je ne recommanderais pas de synchroniser les méthodes elles-mêmes):

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

Pour approfondir votre compréhension:
il existe d'autres moyens (parfois meilleurs) de faire ce qui précède, par exemple avec CountdownLatches, etc. Depuis Java 5, il existe de nombreuses classes de concurrence dans le java.util.concurrentpackage et les sous-packages. Vous avez vraiment besoin de trouver du matériel en ligne pour connaître la concurrence ou obtenir un bon livre.

Herman Lintvelt
la source
Ni tout le code de thread ne peut être bien intégré dans les objets, si je ne me trompe pas. Je ne pense donc pas que l'utilisation de la synchronisation d'objets soit un bon moyen d'implémenter ce travail lié aux threads.
user1914692
@ user1914692: Vous ne savez pas quels sont les pièges de l'utilisation de l'approche ci-dessus - voulez-vous expliquer davantage?
Piskvor a quitté le bâtiment le
1
@Piskvor: Désolé, j'ai écrit ça il y a longtemps et j'ai presque oublié ce que j'avais en tête. Peut-être que je voulais simplement utiliser le verrouillage plutôt que la synchronisation d'objets, cette dernière étant une forme simplifiée de la première.
user1914692
Je ne comprends pas comment cela fonctionne. Si le thread aattend un objet, synchronised(object)comment un autre thread peut-il passer synchronized(object)pour appeler le object.notifyAll()? Dans mon programme, tout est resté bloqué sur des synchronozedblocs.
Tomáš Zato - Réintégrer Monica le
@ TomášZato le premier thread appelle object.wait()le déverrouillage efficace du verrou sur cet objet. Lorsque le deuxième thread "quitte" son bloc synchronisé, les autres objets sont libérés de la waitméthode et obtiennent à nouveau le verrou à ce stade.
rogerdpack
140

Utilisez un CountDownLatch avec un compteur de 1.

CountDownLatch latch = new CountDownLatch(1);

Maintenant, dans le fil de l'application, faites-

latch.await();

Dans le fil db, une fois que vous avez terminé, faites -

latch.countDown();
pdeva
la source
4
J'aime vraiment cette solution pour sa simplicité, bien qu'il puisse être plus difficile de saisir la signification du code à première vue.
guitare létale
3
Cette utilisation vous oblige à refaire le loquet lorsqu'ils sont épuisés. Pour obtenir une utilisation similaire à un événement en attente dans Windows, vous devriez essayer un BooleanLatch ou un CountDownLatch réinitialisable : docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 /…
phyatt
1
Salut, si j'appelle d'abord une méthode async qu'elle supposait déclencher un événement en tant que tel: 1) asyncFunc (); 2) latch.await (); Ensuite, je compte à rebours dans la fonction de gestion des événements une fois qu'il est reçu. Comment puis-je m'assurer que l'événement ne sera pas géré AVANT l'appel de latch.await ()? Je voudrais éviter la préemption entre les lignes 1 et 2. Merci.
NioX5199
1
Pour éviter d'attendre indéfiniment en cas d'erreurs, mettez countDown()en finally{}bloc
Daniel Alder
23

Exigence ::

  1. Attendre l'exécution du thread suivant jusqu'à la fin du précédent.
  2. Le thread suivant ne doit pas démarrer avant l'arrêt du thread précédent, quelle que soit la consommation de temps.
  3. Il doit être simple et facile à utiliser.

Répondre ::

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

future.get () Attend si nécessaire la fin du calcul, puis récupère son résultat.

Travail accompli!! Voir l'exemple ci-dessous

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

Sortie de ce programme:

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

Vous pouvez voir que cela prend 6 secondes avant de terminer sa tâche qui est supérieure à celle des autres threads. Donc Future.get () attend que la tâche soit terminée.

Si vous n'utilisez pas future.get (), il n'attend pas pour terminer et exécute la consommation de temps basée.

Bonne chance avec la concurrence Java.

Cendre
la source
Merci pour votre réponse! J'ai utilisé CountdownLatches, mais la vôtre est une approche beaucoup plus flexible.
Piskvor a quitté le bâtiment le
9

Beaucoup de réponses correctes mais sans exemple simple. Voici un moyen simple et facile à utiliser 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
Maher Abuthraa
la source
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();
        }
    }
}

Utilisez cette classe comme ceci alors:

Créez un ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

Dans la méthode, cela attend des résultats:

resultsReady.await();

Et dans la méthode qui crée les résultats une fois que tous les résultats ont été créés:

resultsReady.signal();

ÉDITER:

(Désolé d'avoir édité ce post, mais ce code a une très mauvaise condition de course et je n'ai pas assez de réputation pour commenter)

Vous ne pouvez l'utiliser que si vous êtes sûr à 100% que signal () est appelé après await (). C'est la seule grande raison pour laquelle vous ne pouvez pas utiliser d'objet Java comme par exemple les événements Windows.

Si le code s'exécute dans cet ordre:

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

alors le fil 2 attendra indéfiniment . En effet, Object.notify () ne réveille qu'un des threads en cours d'exécution. Un thread qui attend plus tard n'est pas réveillé. Ceci est très différent de la façon dont je m'attends à ce que les événements fonctionnent, où un événement est signalé jusqu'à ce que a) attendu ou b) explicitement réinitialisé.

Remarque: la plupart du temps, vous devez utiliser notifyAll (), mais cela n'est pas pertinent pour le problème "attendre pour toujours" ci-dessus.

Kosta
la source
7

Essayez la classe CountDownLatch hors du java.util.concurrentpackage, qui fournit des mécanismes de synchronisation de plus haut niveau, qui sont beaucoup moins sujets aux erreurs que n'importe lequel des éléments de bas niveau.

WMR
la source
6

Vous pouvez le faire en utilisant un objet Exchanger partagé entre les deux threads:

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

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

Et dans le deuxième fil:

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

}

Comme d'autres l'ont dit, ne prenez pas ce code léger et copiez-collez simplement. Lisez d'abord.

kgiannakakis
la source
4

L' interface Future du java.lang.concurrentpackage est conçue pour donner accès aux résultats calculés dans un autre thread.

Jetez un œil à FutureTask et ExecutorService pour une manière toute faite de faire ce genre de chose.

Je recommande vivement de lire Java Concurrency In Practice à toute personne intéressée par la concurrence et le multithreading. Il se concentre évidemment sur Java, mais il y a beaucoup de viande pour quiconque travaille dans d'autres langues aussi.

Bill Michell
la source
2

Si vous voulez quelque chose de rapide et de sale, vous pouvez simplement ajouter un appel Thread.sleep () dans votre boucle while. Si la bibliothèque de base de données est quelque chose que vous ne pouvez pas changer, il n'y a vraiment pas d'autre solution simple. L'interrogation de la base de données jusqu'à ce qu'elle soit prête avec une période d'attente ne tuera pas les performances.

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

Presque quelque chose que vous pourriez appeler un code élégant, mais qui fait le travail.

Dans le cas où vous pouvez modifier le code de la base de données, il est préférable d'utiliser un mutex comme proposé dans d'autres réponses.

Mario Ortegón
la source
3
C'est à peu près occupé à attendre. L'utilisation de constructions à partir des packages util.concurrent de Java 5 devrait être la voie à suivre. stackoverflow.com/questions/289434/… me regarde comme la meilleure solution pour le moment.
Cem Catikkas
Il est occupé à attendre, mais si cela n'est nécessaire que dans cet endroit particulier, et s'il n'y a pas d'accès à la bibliothèque db, que pouvez-vous faire d'autre? Une attente occupée n'est pas nécessairement un mal
Mario Ortegón
2

Ceci s'applique à toutes les langues:

Vous voulez avoir un modèle événement / écouteur. Vous créez un écouteur pour attendre un événement particulier. L'événement serait créé (ou signalé) dans votre thread de travail. Cela bloquera le thread jusqu'à ce que le signal soit reçu au lieu d'interroger constamment pour voir si une condition est remplie, comme la solution que vous avez actuellement.

Votre situation est l'une des causes les plus courantes de blocages - assurez-vous de signaler l'autre thread quelles que soient les erreurs qui se sont produites. Exemple - si votre application lève une exception - et n'appelle jamais la méthode pour signaler à l'autre que les choses sont terminées. Cela fera en sorte que l'autre thread ne se «réveille» jamais.

Je vous suggère de vous pencher sur les concepts d'utilisation d'événements et de gestionnaires d'événements pour mieux comprendre ce paradigme avant de mettre en œuvre votre cas.

Vous pouvez également utiliser un appel de fonction de blocage en utilisant un mutex, ce qui fera attendre le thread pour que la ressource soit libre. Pour ce faire, vous avez besoin d'une bonne synchronisation des threads, par exemple:

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
Klathzazt
la source
2

Vous pouvez lire à partir d'une file d'attente de blocage dans un thread et y écrire dans un autre thread.

Ingo
la source
1

Puisque

  1. join() a été exclu
  2. vous utilisez déjà CountDownLatch et
  3. Future.get () est déjà proposé par d'autres experts,

Vous pouvez envisager d'autres alternatives:

  1. invokeAll deExecutorService

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

    Exécute les tâches données, renvoyant une liste de Futures conservant leur statut et leurs résultats lorsque tout est terminé.

  2. ForkJoinPool ou newWorkStealingPool de Executors(depuis la version Java 8)

    Crée un pool de threads volants en utilisant tous les processeurs disponibles comme niveau de parallélisme cible.

Ravindra babu
la source
-1

entrez la description de l'image ici

Cette idée peut s'appliquer ?. Si vous utilisez CountdownLatches ou Semaphores fonctionne parfaitement, mais si vous cherchez la réponse la plus simple pour une interview, je pense que cela peut s'appliquer.

Franco
la source
1
Comment cela est-il acceptable pour une interview mais pas pour le code réel?
Piskvor a quitté le bâtiment
Parce que dans ce cas s'exécute séquentiellement une attente pour une autre. La meilleure solution pourrait être d'utiliser des sémaphores, car en utilisant CountdownLatches, c'est la meilleure réponse donnée ici, le thread ne s'endort jamais, ce qui signifie l'utilisation de cycles CPU.
Franco
Mais le point n'était pas "les exécuter séquentiellement". Je vais modifier la question pour rendre cela plus clair: le thread GUI attend que la base de données soit prête, puis les deux s'exécutent simultanément pour le reste de l'exécution de l'application: le thread GUI envoie des commandes au thread DB et lit les résultats. (Et encore une fois: quel est le point de code qui peut être utilisé dans une interview mais pas dans le code réel? La plupart des intervieweurs techniques que j'ai rencontrés avaient des connaissances en code et posaient la même question; en plus j'avais besoin de cette chose pour une application réelle J'écrivais à l'époque, pas pour fouiller dans mes devoirs)
Piskvor a quitté le bâtiment
1
Alors. C'est un problème de consommateur de producteur utilisant des sémaphores. Je vais essayer de faire un exemple
Franco
J'ai créé un projet github.com/francoj22/SemProducerConsumer/blob/master/src/com/… . Ça fonctionne bien.
Franco