Maison  >  Article  >  Java  >  Comment terminer un thread en Java

Comment terminer un thread en Java

WBOY
WBOYavant
2023-04-26 08:58:201159parcourir

Le mystère de la désactivation de Thread.stop

À la question de savoir comment mettre fin à un fil de discussion, la plupart des gens savent probablement que vous pouvez appeler la méthode Thread.stop.

Mais cette méthode n'est plus recommandée après jdk1.2. Pourquoi n'est-elle pas recommandée ?

Regardons d'abord la définition de cette méthode :

  @Deprecated(since="1.2")
    public final void stop() {
        @SuppressWarnings("removal")
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            checkAccess();
            if (this != Thread.currentThread()) {
                security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
            }
        }
        // A zero status value corresponds to "NEW", it can't change to
        // not-NEW because we hold the lock.
        if (threadStatus != 0) {
            resume(); // Wake up thread if it was suspended; no-op otherwise
        }

        // The VM can handle all thread states
        stop0(new ThreadDeath());
    }

Nous pouvons voir dans le code que la méthode stop détecte d'abord s'il existe une autorisation d'accès au thread. Si vous disposez de l'autorisation, déterminez si le thread actuel est un thread nouvellement créé. Sinon, appelez la méthode de reprise pour libérer l'état suspendu du thread.

Enfin, appelez la méthode stop0 pour terminer le fil de discussion.

Resume et stop0 sont deux méthodes natives, et l'implémentation spécifique ne sera pas discutée ici.

Il semble que la méthode d'arrêt soit raisonnable et qu'il n'y ait aucun problème. Alors pourquoi cette méthode est-elle dangereuse ?

Regardons ensuite un exemple.

Nous créons une classe NumberCounter. Cette classe a une méthode sûre d'augmentationNumber, qui est utilisée pour ajouter un au nombre :

public class NumberCounter {
    //要保存的数字
    private volatile int number=0;
    //数字计数器的逻辑是否完整
    private volatile boolean flag = false;

    public synchronized int increaseNumber() throws InterruptedException {
        if(flag){
            //逻辑不完整
            throw new RuntimeException("逻辑不完整,数字计数器未执行完毕");
        }
        //开始执行逻辑
        flag = true;
        //do something
        Thread.sleep(5000);
        number++;
        //执行完毕
        flag=false;
        return number;
    }
}

En fait, dans le travail réel, comme une méthode L'exécution peut prendre beaucoup de temps, nous simulons donc ici cette opération fastidieuse en appelant Thread.sleep.

Ici, nous avons également un paramètre flag pour marquer si la méthode IncreaseNumber a été exécutée avec succès.

OK, ensuite nous appelons la méthode de cette classe dans un fil et voyons ce qui se passe :

    public static void main(String[] args) throws InterruptedException {
        NumberCounter numberCounter= new NumberCounter();
        Thread thread = new Thread(()->{
            while (true){
                try {
                    numberCounter.increaseNumber();
                } catch (InterruptedException e) {
                   e.printStackTrace();
                }
            }
        });
        thread.start();
        Thread.sleep(3000);
        thread.stop();
        numberCounter.increaseNumber();
    }

Ici, nous créons un fil et attendons ce fil Après avoir couru pendant 3 secondes, la méthode thread.stop est appelée directement, et on retrouve l'exception suivante :

Exception dans le thread "main" java.lang.RuntimeException : La logique est incomplète, Le compteur numérique n'a pas été exécuté
sur com.flydean.NumberCounter.increaseNumber(NumberCounter.java:12)
sur com.flydean.Main.main(Main.java:18)

#🎜🎜 #
Cela est dû au fait que la méthode thread.stop termine directement l'exécution du thread, ce qui fait que mberCounter.increaseNumber n'est pas terminé.

Mais cet état inachevé est masqué. Si vous utilisez la méthode thread.stop pour terminer le fil, cela entraînera probablement des résultats inconnus.

Donc, nous disons que thread.stop n'est pas sûr.

Comment être en sécurité

Donc, si la méthode thread.stop n'est pas appelée, comment pouvons-nous terminer le thread en toute sécurité ?

La soi-disant sécurité signifie que la logique du thread doit être complètement exécutée, et non à moitié exécutée.

Pour obtenir cet effet, Thread nous propose trois méthodes similaires, qui sont interrompues, interrompues et isInterrupted.

interrupt consiste à définir l'indicateur d'interruption pour le thread ; interrompu consiste à détecter l'interruption et à effacer l'état de l'interruption ; isInterrupted détecte uniquement l'interruption. Un autre point important est qu'interrupted est une méthode de classe qui agit sur le thread actuel interrompu et isInterrupted agit sur ce thread, c'est-à-dire le thread représenté par l'instance qui appelle cette méthode dans le code.

interrupt est la méthode d'interruption. Son workflow est le suivant :

  • Si l'instance de thread actuelle appelle wait() de la classe Object. , la méthode wait (long) ou wait(long, int) ou join(), join(long), join(long, int) ou Thread.sleep(long) ou Thread.sleep(long, int) a été appelé dans cette instance) et est dans l'état de blocage, son état d'interruption sera effacé et une InterruptedException sera reçue.

  • Si ce thread est bloqué lors d'une opération d'E/S sur InterruptibleChannel, le canal sera fermé et l'état d'interruption du thread sera défini sur true , et le thread recevra une exception java.nio.channels.ClosedByInterruptException.

  • Si ce fil est bloqué dans java.nio.channels.Selector, l'état d'interruption du fil sera défini sur vrai et il démarrera immédiatement de Renvoyé pendant sélectionner l’opération.

  • Si aucune des conditions ci-dessus n'est vraie, définissez l'état d'interruption sur vrai.

Dans l'exemple ci-dessus, dans la méthode IncreaseNumber de NumberCounter, nous avons appelé la méthode Thread.sleep, donc si à ce moment, la méthode d'interruption du thread est appelée, le fil Une InterruptedException sera levée.

Nous modifions l'exemple d'appel ci-dessus comme suit :

    public static void main(String[] args) throws InterruptedException {
        NumberCounter numberCounter = new NumberCounter();

        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    numberCounter.increaseNumber();
                } catch (InterruptedException e) {
                    System.out.println("捕获InterruptedException");
                    throw new RuntimeException(e);
                }
            }
        });

        thread.start();
        Thread.sleep(500);
        thread.interrupt();
        numberCounter.increaseNumber();
    }

Réessayez après avoir exécuté :

Exception in thread "main" Exception in thread "Thread-0" java.lang.RuntimeException: 逻辑不完整,数字计数器未执行完毕
    at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:12)
    at com.flydean.Main2.main(Main2.java:21)
java.lang.RuntimeException: java.lang.thread.interrupt: sleep interrupted
    at com.flydean.Main2.lambda$main$0(Main2.java:13)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InterruptedException: sleep interrupted
    at java.base/java.lang.Thread.sleep(Native Method)
    at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:17)
    at com.flydean.Main2.lambda$main$0(Main2.java:10)
    ... 1 more
捕获InterruptedException

可以看到,我们捕获到了这个InterruptedException,并且得知具体的原因是sleep interrupted。

捕获异常之后的处理

从上面的分析可以得知,thread.stop跟thread.interrupt的表现机制是不一样的。thread.stop属于悄悄终止,我们程序不知道,所以会导致数据不一致,从而产生一些未知的异常。

而thread.interrupt会显示的抛出InterruptedException,当我们捕捉到这个异常的时候,我们就知道线程里面的逻辑在执行的过程中受到了外部作用的干扰,那么我们就可以执行一些数据恢复或者数据校验的动作。

在上面的代码中,我们是捕获到了这个异常,打印出异常日志,然后向上抛出一个RuntimeException。

正常情况下我们是需要在捕获异常之后,进行一些处理。

那么自己处理完这个异常之后,是不是就完美了呢?

答案是否定的。

因为如果我们自己处理了这个InterruptedException, 那么程序中其他部分如果有依赖这个InterruptedException的话,就可能会出现数据不一致的情况。

所以我们在自己处理完InterruptedException之后,还需要再次抛出这个异常。

怎么抛出InterruptedException异常呢?

有两种方式,第一种就是在调用Thread.interrupted()清除了中断标志之后立即抛出:

   if (Thread.interrupted())  // Clears interrupted status!
       throw new InterruptedException();

还有一种方式就是,在捕获异常之后,调用Thread.currentThread().interrupt()再次中断线程。

public void run () {
  try {
    while (true) {
      // do stuff
    }
  }catch (InterruptedException e) {
    LOGGER.log(Level.WARN, "Interrupted!", e);
    // Restore interrupted state...
    Thread.currentThread().interrupt();
  }
}

这两种方式都能达到预想的效果。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer