web-dev-qa-db-fra.com

Comment faire en sorte qu'un thread Java attende la sortie d'un autre thread?

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 être exécutés en même temps (on parle au serveur, on parle à l'utilisateur; lorsque l'application est complètement lancée, j'ai besoin de both = d'eux pour travailler).

Cependant, au démarrage, je dois m'assurer que le thread de l'application attend au départ que le thread de la base de données soit prêt (actuellement déterminé par l'interrogation d'une méthode personnalisée dbthread.isReady()). Cela ne me dérangerait pas que le fil d'application bloque jusqu'à ce que le fil de base de données soit prêt.

Thread.join() ne ressemble pas à une solution - le thread de base de données ne se ferme qu'à la fermeture de l'application.

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

D'autres idées? Merci.

123
Piskvor

Je vous recommanderais vraiment de suivre un didacticiel tel que La concurrence de Java de Sun avant de commencer dans le monde magique du multithreading.

Il existe également un certain nombre de bons livres (Google pour "Programmation simultanée en 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 la méthode de votre dbThread, vous devez 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

La objectYouNeedToLockOn que j'utilise dans ces exemples est de préférence l'objet que vous devez manipuler simultanément à partir de chaque thread, ou vous pouvez créer un Object séparé à cet effet (je ne recommanderais pas de créer les méthodes elles-mêmes. synchronisé):

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

Pour approfondir votre compréhension:
Il existe d’autres manières (parfois meilleures) de procéder comme indiqué ci-dessus, par exemple: avec CountdownLatches, etc. Depuis Java 5, le paquet Java.util.concurrent contient beaucoup de classes de concurrence simultanées astucieuses. Vous devez vraiment trouver du matériel en ligne pour apprendre à connaître la concurrence ou obtenir un bon livre.

126
Herman Lintvelt

Utilisez un CountDownLatch avec un compteur de 1.

CountDownLatch latch = new CountDownLatch(1);

Maintenant, dans le fil de l'application

latch.await();

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

latch.countDown();
133
pdeva

Condition ::

  1. Pour attendre l'exécution du prochain thread jusqu'à la fin précédente.
  2. Le thread suivant ne doit pas démarrer avant que le thread précédent ne s'arrête, 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 que le calcul soit terminé, puis récupère son résultat.

Travail accompli!! Voir 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 plus grande que les 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 Java concurrence.

22
Ash
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 événement ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

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

resultsReady.await();

Et dans la méthode qui crée les résultats après la création de tous les résultats:

resultsReady.signal();

EDIT:

(Désolé pour l'édition de ce post, mais ce code a une très mauvaise condition de compétition 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 () sera appelé après wait (). C'est la principale raison pour laquelle vous ne pouvez pas utiliser l'objet Java comme par exemple. Événements Windows.

Le si le code est exécuté dans cet ordre:

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

alors le fil 2 attendra pour toujours. En effet, Object.notify () réveille uniquement l’un des threads en cours d’exécution. Un fil d'attente plus tard n'est pas réveillé. Ceci est très différent de la manière dont je m'attends à ce que les événements fonctionnent, où un événement est signalé jusqu'à ce que a) attendu ou b) soit 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.

8
Lourens Coetzer

Essayez la classe CountDownLatch du package Java.util.concurrent, qui fournit des mécanismes de synchronisation de niveau supérieur, beaucoup moins sujets aux erreurs que les éléments de bas niveau.

7
WMR

Beaucoup de réponses correctes mais sans exemple simple. Voici un moyen simple et facile d’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
6
Maher Abuthraa

Vous pouvez le faire en utilisant un objet Echangeur 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-simplement du code. Faites de la lecture en premier.

6
kgiannakakis

L'interface Future du package Java.lang.concurrent est conçue pour fournir un accès aux résultats calculés dans un autre thread.

Jetez un oeil à FutureTask et ExecutorService pour une méthode toute prête de ce type de travail.

Je recommande fortement de lire Java Concurrency in Practice à toute personne intéressée par la simultanéité et le multithreading. Il se concentre évidemment sur Java, mais il y a beaucoup de viande pour ceux qui travaillent dans d'autres langues.

4
Bill Michell

Ceci s'applique à toutes les langues:

Vous voulez avoir un modèle d'événement/auditeur. 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'à la réception du signal au lieu de scruter en permanence pour vérifier si une condition est remplie, comme la solution que vous avez actuellement.

Votre situation est l’une des causes les plus courantes d’interblocages. Assurez-vous de signaler l’autre thread, quelles que soient les erreurs éventuelles. Exemple - si votre application lève une exception - et n'appelle jamais la méthode pour signaler à l'autre que les tâches sont terminées. Cela fera en sorte que l'autre thread ne se "réveille" jamais.

Je vous suggère d'examiner 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 à l'aide d'un mutex, ce qui obligera le thread à attendre que la ressource soit libre. Pour ce faire, vous avez besoin d'une bonne synchronisation de threads, telle que:

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
Klathzazt

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 modifier, il n’existe vraiment aucune autre solution simple. Interroger la base de données jusqu'à ce qu'il soit prêt avec une période d'attente ne gâchera pas la performance.

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

C'est à peine quelque chose que l'on pourrait appeler un code élégant, mais qui fait le travail.

Si 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.

2
Mario Ortegón

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

2
Ingo

Puisque

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

Vous pouvez envisager d'autres alternatives:

  1. invokeAll de ExecutorService

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

    Exécute les tâches données, en renvoyant une liste des contrats à terme contenant leur statut et leurs résultats lorsque tous sont terminés.

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

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

1
Ravindra babu