web-dev-qa-db-fra.com

Comment abandonner un fil de manière rapide et propre en Java?

Voici mon problème: j'ai une boîte de dialogue avec quelques paramètres que l'utilisateur peut modifier (via un spinner par exemple). Chaque fois que l'un de ces paramètres est modifié, je lance un thread pour mettre à jour une vue 3D en fonction de la nouvelle valeur du paramètre. Si l'utilisateur modifie une autre valeur (ou encore la même valeur en cliquant plusieurs fois sur la flèche du spinner) pendant que le premier thread fonctionne, je voudrais abandonner le premier thread (et la mise à jour de la vue 3D) et en lancer un nouveau avec la dernière valeur de paramètre.

Comment puis-je faire quelque chose comme ça?

PS: Il n'y a pas de boucle dans la méthode run() de mon thread, donc la recherche d'un indicateur n'est pas une option: le thread qui met à jour la vue 3D n'appelle fondamentalement qu'une seule méthode très longue à exécuter. Je ne peux pas ajouter d'indicateur dans cette méthode demandant d'interrompre non plus car je n'ai pas accès à son code.

28
jumar

Essayez interrupt () comme certains l'ont dit pour voir si cela fait une différence pour votre thread. Sinon, essayez de détruire ou de fermer une ressource qui fera arrêter le thread. Cela a une chance d'être un peu mieux que d'essayer de lui lancer Thread.stop ().

Si les performances sont tolérables, vous pouvez afficher chaque mise à jour 3D comme un événement discret non interruptible et simplement le laisser s'exécuter jusqu'à sa conclusion, en vérifiant ensuite s'il y a une nouvelle mise à jour à effectuer. Cela pourrait rendre l'interface graphique un peu saccadée pour les utilisateurs, car ils pourraient apporter cinq modifications, puis voir les résultats graphiques de la façon dont les choses étaient il y a cinq changements, puis voir le résultat de leur dernier changement. Mais en fonction de la durée de ce processus, il pourrait être tolérable et éviterait de devoir tuer le thread. Le design pourrait ressembler à ceci:

boolean stopFlag = false;
Object[] latestArgs = null;

public void run() {
  while (!stopFlag) {
    if (latestArgs != null) {
      Object[] args = latestArgs;
      latestArgs = null;
      perform3dUpdate(args);
    } else {
      Thread.sleep(500);
    }
  }
}

public void endThread() {
  stopFlag = true;
}

public void updateSettings(Object[] args) {
  latestArgs = args;
}
12
skiphoppy

Le thread qui met à jour la vue 3D doit vérifier périodiquement un indicateur (utilisez un volatile boolean) Pour voir s'il doit se terminer. Lorsque vous souhaitez abandonner le thread, définissez simplement le drapeau. Lorsque le thread vérifie ensuite l'indicateur, il doit simplement sortir de la boucle qu'il utilise pour mettre à jour la vue et revenir de sa méthode run.

Si vous ne pouvez vraiment pas accéder au code que le thread exécute pour qu'il vérifie un indicateur, il n'y a aucun moyen sûr d'arrêter le thread. Ce thread se termine-t-il normalement avant la fin de votre application? Si oui, qu'est-ce qui fait que cela s'arrête?

S'il s'exécute pendant une longue période et que vous devez simplement y mettre fin, vous pouvez envisager d'utiliser la méthode Thread.stop() obsolète. Cependant, il était obsolète pour une bonne raison. Si ce thread est arrêté pendant une opération qui laisse quelque chose dans un état incohérent ou une ressource non nettoyée correctement, alors vous pourriez avoir des ennuis. Voici une note de la documentation :

Cette méthode est intrinsèquement dangereuse. L'arrêt d'un thread avec Thread.stop provoque le déverrouillage de tous les moniteurs qu'il a verrouillés (comme conséquence naturelle de l'exception ThreadDeath non vérifiée se propageant dans la pile). Si l'un des objets précédemment protégés par ces moniteurs était dans un état incohérent, les objets endommagés deviennent visibles pour les autres threads, ce qui peut entraîner un comportement arbitraire. De nombreuses utilisations de stop doivent être remplacées par du code qui modifie simplement une variable pour indiquer que le thread cible doit cesser de fonctionner. Le thread cible doit vérifier cette variable régulièrement et revenir de sa méthode d'exécution de manière ordonnée si la variable indique qu'elle doit s'arrêter. Si le thread cible attend pendant de longues périodes (sur une variable de condition, par exemple), la méthode d'interruption doit être utilisée pour interrompre l'attente. Pour plus d'informations, consultez Pourquoi Thread.stop, Thread.suspend et Thread.resume sont-ils obsolètes?

8
Dave L.

Au lieu de lancer votre propre drapeau booléen, pourquoi ne pas simplement utiliser le mécanisme d'interruption de threads déjà dans les threads Java? Selon la façon dont les internes ont été implémentés dans le code que vous ne pouvez pas changer, vous pourrez peut-être d'interrompre également une partie de son exécution.

Fil extérieur:

if(oldThread.isRunning())
{
    oldThread.interrupt();
    // Be careful if you're doing this in response to a user
    // action on the Event Thread
    // Blocking the Event Dispatch Thread in Java is BAD BAD BAD
    oldThread.join();
}

oldThread = new Thread(someRunnable);
oldThread.start();

Runnable/Thread intérieur:

public void run()
{
    // If this is all you're doing, interrupts and boolean flags may not work
    callExternalMethod(args);
}

public void run()
{
    while(!Thread.currentThread().isInterrupted)
    {
        // If you have multiple steps in here, check interrupted peridically and
        // abort the while loop cleanly
    }
}
4
basszero

N'est-ce pas un peu comme demander "Comment puis-je abandonner un thread quand aucune méthode autre que Thread.stop () n'est disponible?"

De toute évidence, la seule réponse valable est Thread.stop (). Son laid, pourrait casser des choses dans certaines circonstances, peut entraîner des fuites de mémoire/ressources, et est mal vu par TLEJD (The League of Extraordinary Java Developers), mais il peut toujours être utile dans un quelques cas comme celui-ci. Il n'y a vraiment aucune autre méthode si le code tiers n'a pas de méthode proche à sa disposition.

OTOH, il existe parfois des méthodes de fermeture de porte dérobée. C'est-à-dire, la fermeture d'un flux sous-jacent avec lequel il travaille ou d'une autre ressource dont il a besoin pour faire son travail. C'est rarement mieux que d'appeler simplement Thread.stop () et de lui laisser faire l'expérience d'une ThreadDeathException, cependant.

3
jsight

La réponse acceptée à cette question vous permet de soumettre un travail par lots dans un fil d'arrière-plan. Cela pourrait être un meilleur modèle pour cela:

public abstract class dispatcher<T> extends Thread {

  protected abstract void processItem(T work);

  private List<T> workItems = new ArrayList<T>();
  private boolean stopping = false;
  public void submit(T work) {
    synchronized(workItems) {
      workItems.add(work);
      workItems.notify();
    }
  }
  public void exit() {
    stopping = true;
    synchronized(workItems) {
      workItems.notifyAll();
    }
    this.join();
  }
  public void run() {
    while(!stopping) {
      T work;
      synchronized(workItems) {
        if (workItems.empty()) {
            workItems.wait();
            continue;
        }
        work = workItems.remove(0);
      }
      this.processItem(work);
    }
  }
}

Pour utiliser cette classe, étendez-la en fournissant un type pour T et une implémentation de processItem (). Il vous suffit ensuite d'en construire un et d'appeler start () dessus.

Vous pourriez envisager d'ajouter une méthode abortPending:

public void abortPending() {
  synchronized(workItems) {
    workItems.clear();
  }
}

pour les cas où l'utilisateur a ignoré le moteur de rendu et que vous souhaitez supprimer le travail planifié jusqu'à présent.

2
nsayer

Les solutions qui visent l'utilisation d'un champ booléen sont la bonne direction. Mais le champ doit être volatil. La spécification de langue Java Java dit :

"Par exemple, dans le fragment de code (cassé) suivant, supposons que this.done est un champ booléen non volatile:

while (! this.done) 
 Thread.sleep (1000); 

Le compilateur est libre de lire le champ this.done une seule fois et de réutiliser la valeur mise en cache dans chaque exécution de la boucle. Cela signifierait que la boucle ne se terminerait jamais, même si un autre thread modifiait la valeur de this.done. "

Pour autant que je me souvienne "Java Concurrence in Pratice" fins d'utiliser les méthodes interrupt () et interrupted () de Java.lang.Thread .

1
dmeister

La façon dont j'ai implémenté quelque chose comme ça dans le passé est d'implémenter une méthode shutdown() dans ma sous-classe Runnable qui définit une variable d'instance appelée should_shutdown Sur true. La méthode run() fait normalement quelque chose dans une boucle et vérifie périodiquement should_shutdown Et quand elle est vraie, retourne ou appelle do_shutdown() puis retourne.

Vous devez conserver une référence au thread de travail actuel à portée de main et lorsque l'utilisateur modifie une valeur, appelez shutdown() sur le thread actuel et attendez qu'il s'arrête. Ensuite, vous pouvez lancer un nouveau fil.

Je ne recommanderais pas d'utiliser Thread.stop Car il était obsolète la dernière fois que j'ai vérifié.

Modifier:

Lisez votre commentaire sur la façon dont votre thread de travail appelle simplement une autre méthode qui prend un certain temps à s'exécuter, donc ce qui précède ne s'applique pas. Dans ce cas, vos seules options réelles sont d'essayer d'appeler interrupt() et de voir si cela a un effet. Sinon, envisagez de provoquer une interruption manuelle de la fonction que votre thread de travail appelle. Par exemple, il semble qu'il fasse un rendu complexe, alors détruisez peut-être le canevas et faites-lui lever une exception. Ce n'est pas une bonne solution, mais pour autant que je sache, c'est la seule façon d'arrêter un fil dans des situations comme celle-ci.

1
freespace

Un thread se fermera une fois que la méthode run() sera terminée, donc vous avez besoin d'une vérification qui lui fera terminer la méthode.

Vous pouvez interrompre le thread, puis effectuer une vérification qui vérifierait périodiquement isInterrupted() et sortirait de la méthode run().

Vous pouvez également utiliser un booléen qui est vérifié périodiquement dans le thread et le faire revenir si c'est le cas, ou placer le thread dans une boucle s'il effectue une tâche répétitive et il quittera ensuite la méthode run () lorsque vous définissez le booléen. Par exemple,

static boolean shouldExit = false;
Thread t = new Thread(new Runnable() {
    public void run() {
        while (!shouldExit) {
            // do stuff
        }
    }
}).start();
1
Rich Adams

Vous semblez n'avoir aucun contrôle sur le thread qui rend l'écran mais vous semblez avoir le contrôle du composant spinner. Je voudrais désactiver le spinner pendant que le fil rend l'écran. De cette façon, l'utilisateur a au moins quelques commentaires concernant ses actions.

1
Javamann

Je suggère que vous empêchiez simplement plusieurs threads en utilisant attendre et notifier afin que si l'utilisateur modifie la valeur plusieurs fois, il n'exécutera le thread qu'une seule fois. Si les utilisateurs modifient la valeur 10 fois, il déclenchera le fil lors de la première modification, puis toutes les modifications apportées avant que le fil ne soit terminé seront toutes "regroupées" en une seule notification. Cela n'arrêtera pas un fil, mais il n'y a pas de bons moyens de le faire en fonction de votre description.

1

Malheureusement, tuer un thread est intrinsèquement dangereux en raison des possibilités d'utiliser des ressources qui peuvent être synchronisées par des verrous et si le thread que vous tuez actuellement a un verrou, le programme pourrait se retrouver dans un blocage (tentative constante de récupérer une ressource qui ne peut pas être obtenue) . Vous devrez vérifier manuellement s'il doit être supprimé du thread que vous souhaitez arrêter. Volatile assurera la vérification de la vraie valeur de la variable plutôt que quelque chose qui peut avoir été stocké précédemment. Sur une note latérale, Thread.join sur le fil sortant pour vous assurer d'attendre que le fil mourant soit réellement parti avant de faire quoi que ce soit plutôt que de vérifier tout le temps.

1
bmeck

Puisque vous avez affaire à du code auquel vous n'avez pas accès, vous n'avez probablement pas de chance. La procédure standard (comme indiqué dans les autres réponses) est d'avoir un indicateur qui est vérifié périodiquement par le thread en cours d'exécution. Si l'indicateur est défini, effectuez le nettoyage et quittez.

Étant donné que cette option n'est pas disponible pour vous, la seule autre option consiste à forcer la fermeture du processus en cours. Auparavant, cela était possible en appelant Thread.stop () , mais cette méthode a été définitivement déconseillée pour la raison suivante (copiée à partir des javadocs):

Cette méthode est intrinsèquement dangereuse. L'arrêt d'un thread avec Thread.stop provoque le déverrouillage de tous les moniteurs qu'il a verrouillés (comme conséquence naturelle de l'exception ThreadDeath non vérifiée se propageant dans la pile). Si l'un des objets précédemment protégés par ces moniteurs était dans un état incohérent, les objets endommagés deviennent visibles pour les autres threads, ce qui peut entraîner un comportement arbitraire.

Plus d'informations sur ce sujet peuvent être trouvées ici .

Une façon sûre et absolue de répondre à votre demande (bien que ce ne soit pas un moyen très efficace de le faire) est de démarrer un nouveau processus Java via Runtime.exec () = puis arrêter ce processus si nécessaire via Process.destroy () . Le partage de l'état entre des processus comme celui-ci n'est pas exactement trivial, cependant.

0
toluju

La bonne réponse est de ne pas utiliser de fil.

Vous devez utiliser des exécuteurs, voir le package: Java.util.concurrent

0
Pyrolistical

Peut-être que cela peut vous aider: Comment pouvons-nous tuer un thread en cours d'exécution en Java?

Vous pouvez tuer un thread particulier en définissant une variable de classe externe.

 Class Outer
 {    
    public static flag=true;
    Outer()
    {
        new Test().start();
    } 
    class Test extends Thread
    {               
       public void run()
       {
         while(Outer.flag)
         {
          //do your work here
         }  
       }
    }
  } 

si vous voulez arrêter le thread ci-dessus, définissez la variable indicateur sur false. L'autre façon de tuer un thread est simplement de l'enregistrer dans ThreadGroup, puis d'appeler destroy(). Cette façon peut également être utilisée pour tuer des threads similaires en les créant en tant que groupe ou en vous inscrivant avec le groupe.

0
Olvagor

Au lieu de jouer avec le démarrage et l'arrêt du thread, avez-vous envisagé de faire en sorte que le thread observe les propriétés que vous modifiez via votre interface? À un moment donné, vous souhaiterez toujours une condition d'arrêt pour votre thread, mais cela peut également être le cas. Si vous êtes un fan de MVC, cela s'intègre parfaitement dans ce type de conception

Désolé, après avoir relu votre question, ni cela ni aucune des autres suggestions de "vérification de variable" ne résoudra votre problème.

0
Nerdfest