Maison  >  Article  >  Java  >  Explication graphique détaillée du cycle de vie et du contrôle d'état dans les threads Java

Explication graphique détaillée du cycle de vie et du contrôle d'état dans les threads Java

黄舟
黄舟original
2017-05-21 10:25:212064parcourir

Cet article présente principalement le cycle de vie et le statut contrôle des threads Java. Les amis dans le besoin peuvent se référer à

1. Discussion. cycle de vie

Diagramme de transition de l'état du thread :

Explication graphique détaillée du cycle de vie et du contrôle détat dans les threads Java

Nouvel état

Après avoir utilisé le mot-clé new et la classe Thread ou sa sous-classe pour créer un objet thread , l'objet thread est dans un nouvel état. Le thread dans le nouvel état dispose de son propre espace mémoire et entre dans l'état prêt (exécutable) en appelant la méthode start.

Remarque : La méthode start() ne peut pas être appelée à nouveau sur un thread déjà démarré, sinon une exception Javalang.IllegalThreadStateException se produira.

2. État prêt

Le thread à l'état prêt a les conditions d'exécution, mais n'a pas encore été alloué au CPU. le thread est prêt File d'attente (Bien qu'il se présente sous la forme d'une file d'attente, en fait, on l'appelle un pool exécutable plutôt qu'une file d'attente exécutable. Parce que la planification du CPU n'est pas nécessairement planifiée dans un premier temps. dans l'ordre premier sorti), le système en attente est Il alloue le CPU. L'état d'attente n'est pas l'état d'exécution.Lorsque le système sélectionne un objet Thread en attente d'exécution, il entrera dans l'état d'exécution à partir de l'état d'exécution en attente. L'action sélectionnée par le système est appelée « planification du processeur ». Une fois qu'il obtient le processeur, le thread entre dans l'état d'exécution et appelle automatiquement sa propre méthode d'exécution.

Astuce : Si vous souhaitez que le sous-thread s'exécute immédiatement après avoir appelé la méthode start(), vous pouvez utiliser la méthode Thread.sleep() pour faire dormir le thread principal ensemble, puis exécuter le sous-thread. .

3. État en cours d'exécution

Le thread en cours d'exécution est le plus complexe, et il peut devenir bloqué, prêt et mort. Si un thread à l'état prêt est planifié par le CPU, il passera de l'état prêt à l'état en cours d'exécution et exécutera les tâches dans la méthode run(). Si le thread perd des ressources CPU, il passera de l'état d'exécution à l'état prêt. Attendez que le système alloue à nouveau des ressources. Vous pouvez également appeler la méthode rendement() sur un thread en cours d'exécution, ce qui abandonnera les ressources CPU et redeviendra prêt.

Lorsque les situations suivantes se produisent, le thread passe de l'état d'exécution à l'état bloqué :

① Le thread appelle la méthode sleep pour abandonner activement le. ressources système occupées

②. Le thread appelle une méthode IO bloquante avant le retour de la méthode, le thread est bloqué

③. est détenu par d'autres threads. Il y a

④, le fil attend une notification (pasify)

⑤, le programme appelle le suspend méthode du fil pour mettre fin au blocage du fil. Cependant, cette méthode peut facilement conduire à un blocage, les programmes doivent donc essayer d'éviter de l'utiliser.

Lorsque la méthode run() du thread est exécutée ou est terminée de force, par exemple, une exception se produit ou la méthode stop(), desyory(), etc. , il passera de l'état en cours d'exécution à l'état mort.

4. État bloquant

Un thread en état d'exécution peut, dans certaines circonstances, comme l'exécution de la méthode sleep (sleep), ou en attente Les ressources telles que les périphériques d'E/S abandonneront le processeur, arrêteront temporairement leur propre fonctionnement et entreront dans un état de blocage.
Les threads bloqués ne peuvent pas entrer dans la file d'attente prête. Ce n'est que lorsque la cause du blocage est éliminée, par exemple que le temps de veille a expiré ou que le périphérique d'E/S en attente est inactif, que le thread entrera dans l'état prêt, se mettra à nouveau en file d'attente dans la file d'attente prête et repartira de l'arrêt d'origine. position après avoir été sélectionné par le système.

5. État de mort

Lorsque la méthode run() du thread est exécutée ou est terminée de force, elle est considérée comme morte. Cet objet thread peut être vivant, mais ce n'est plus un thread exécuté séparément. Une fois qu'un fil meurt, il ne peut plus être relancé. Si la méthode start() est appelée sur un thread mort, une exception java.lang.IllegalThreadStateException sera levée.

2. Contrôle de l'état des threads

Java fournit des méthodes pratiques pour contrôler l'état des threads.

Explication graphique détaillée du cycle de vie et du contrôle détat dans les threads Java.

Vous pouvez voir que de nombreuses méthodes ont été marquées comme obsolètes. Nous devrions éviter de les utiliser autant que possible et devrions nous concentrer sur start(), interrompu(), join(). , Méthodes de contrôle direct telles que sleep() et rendement(), et méthodes de contrôle indirect telles que setDaemon() et setPriority().

1. Veille du fil - veille

Si nous devons laisser le fil en cours d'exécution faire une pause pendant un certain temps et entrer dans un état de blocage , alors vous pouvez appeler la méthode sleep de Thread. Comme vous pouvez le voir ci-dessus, la méthode sleep a deux formes de surcharge , mais la méthode d'utilisation est la même.

Par exemple, nous voulons mettre le fil principal en veille toutes les 100 millisecondes, puis imprimer les chiffres :


 public class Test { 
  public static void main(String[] args) throws InterruptedException { 
   for(int i=;i<;i++){ 
    System.out.println("main"+i); 
    Thread.sleep(); 
   } 
  } 
 }

Vous pouvez voir clairement le numéros imprimés Il y a un léger décalage dans le temps.

Faites attention aux problèmes suivants

① sleep est une méthode statique Il est préférable de ne pas l'appeler avec un objet instance de. Le thread, car il dort, est toujours le thread en cours d'exécution, pas l'objet thread qui l'appelle. Il n'est valable que pour l'objet thread en cours d'exécution. Regardez l'exemple suivant :


public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   System.out.println(Thread.currentThread().getName()); 
   MyThread myThread=new MyThread(); 
   myThread.start(); 
   myThread.sleep(1000);//这里sleep的就是main线程,而非myThread线程 
   Thread.sleep(10); 
   for(int i=0;i<100;i++){ 
    System.out.println("main"+i); 
   } 
  } 
 }

②. La planification des threads Java est au cœur du multi-threading Java. Seule une bonne planification peut exploiter pleinement les performances du. système et améliorer l’efficacité de l’exécution du programme. Cependant, quelle que soit la manière dont le programmeur écrit le calendrier, il ne peut affecter que l'ordre d'exécution des threads au maximum, mais ne peut pas obtenir un contrôle précis. Parce qu'après avoir utilisé la méthode sleep, le thread entre dans l'état de blocage. Ce n'est que lorsque le temps de veille est terminé qu'il entrera à nouveau dans l'état prêt. La transition de l'état prêt à l'état d'exécution est contrôlée par le système. avec précision, donc si Thread.sleep(1000) est appelé pour faire dormir le thread pendant 1 seconde, le résultat peut être supérieur à 1 seconde.


public class Test1 { 
public static void main(String[] args) throws InterruptedException { 
   new MyThread().start(); 
new MyThread().start(); 
  } 
 }  
 class MyThread extends Thread { 
  @Override 
  public void run() { 
   for (int i = 0; i < 3; i++) { 
    System.out.println(this.getName()+"线程" + i + "次执行!"); 
    try { 
     Thread.sleep(50); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 
   } 
  } 
 }

Regardez les résultats d'une certaine opération :

1 thread-0 a été exécuté 0 fois !
2. Le thread Thread-1 est exécuté 0 fois !
3. Le thread Thread-1 est exécuté une fois !
4. Le thread Thread-0 est exécuté une fois !
5. Le thread Thread-0 est exécuté deux fois !
6. Le thread Thread-1 est exécuté deux fois !

Comme vous pouvez le voir, le thread 0 est exécuté en premier, puis le thread 1 est exécuté une fois, puis à nouveau. Vous pouvez voir qu'il n'est pas exécuté dans l'ordre du sommeil.

2. Concession de thread - rendement

La méthode rendement() est quelque peu similaire à la méthode sleep(), et c'en est aussi une fourni par la méthode statique de la classe Thread, il peut également suspendre le thread en cours d'exécution et céder les ressources CPU à d'autres threads. Mais contrairement à la méthode sleep(), elle n’entre pas dans l’état de blocage, mais entre dans l’état prêt. La méthode rendement() met simplement en pause le thread actuel, réintègre le pool de threads prêt et permet au planificateur de threads du système de le replanifier à nouveau. Il est tout à fait possible que cette situation se produise : lorsqu'un thread appelle la méthode rendement(), le planificateur de threads le planifie et rentre dans l'état d'exécution pour l'exécution.

En fait, lorsqu'un thread appelle la méthode rendement() pour faire une pause, la priorité est la même que celle du thread actuel, ou du thread à l'état prêt avec une priorité plus élevée que la Le thread actuel a plus de possibilités d'exécution. Bien sûr, cela n'est possible que parce que nous ne pouvons pas interférer avec précision avec le thread de planification du processeur.

Utilisation de rendement :


public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   new MyThread("低级", 1).start(); 
   new MyThread("中级", 5).start(); 
   new MyThread("高级", 10).start(); 
  } 
 } 
 class MyThread extends Thread { 
  public MyThread(String name, int pro) { 
   super(name);// 设置线程的名称 
   this.setPriority(pro);// 设置优先级 
  } 
  @Override 
  public void run() { 
   for (int i = 0; i < 30; i++) { 
    System.out.println(this.getName() + "线程第" + i + "次执行!"); 
   if (i % 5 == 0) 
     Thread.yield(); 
   } 
  } 
 }

La différence entre la méthode sleep() et la méthode rendement() est la suivante :

①. Une fois que la méthode sleep a suspendu le thread actuel, il entrera dans l'état de blocage seulement lorsque le temps de veille sera écoulé, il entrera dans l'état prêt. Une fois que la méthode rendement est appelée, elle entre directement dans l'état prêt, elle peut donc être planifiée pour l'état d'exécution juste après être entrée dans l'état prêt.

②. La méthode sleep déclare qu'une InterruptedException est levée, donc lorsque vous appelez la méthode sleep, vous devez intercepter l'exception ou déclarer explicitement que l'exception est levée. La méthode rendement ne déclare pas d’exception de tâche.

③.La méthode sleep a une meilleure portabilité que la méthode Yield. Généralement, ne comptez pas sur la méthode Yield pour contrôler l'exécution des threads simultanés.

3. Fusion de threads - rejoindre

La signification de la fusion de threads est de fusionner plusieurs threads parallèles en un seul thread pour l'exécution Le scénario d'application c'est lorsqu'un thread doit attendre qu'un autre thread termine son exécution avant de pouvoir s'exécuter. La classe Thread fournit la méthode join pour terminer cette fonction.

Comme vous pouvez le voir dans la liste des méthodes ci-dessus, il dispose de 3 méthodes surchargées :

void join()

Fil de discussion actuel, etc. Il est temps de rejoindre le fil de discussion et d'attendre que le fil se termine.

void join(long millis)   

    当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度  

void join(long millis,int nanos)   

    等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度 

例子:


 public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   MyThread thread=new MyThread(); 
   thread.start(); 
   thread.join(1);//将主线程加入到子线程后面,不过如果子线程在1毫秒时间内没执行完,则主线程便不再等待它执行完,进入就绪状态,等待cpu调度 
   for(int i=0;i<30;i++){ 
    System.out.println(Thread.currentThread().getName() + "线程第" + i + "次执行!"); 
   } 
  } 
 } 
 class MyThread extends Thread { 
  @Override 
 public void run() { 
   for (int i = 0; i < 1000; i++) { 
    System.out.println(this.getName() + "线程第" + i + "次执行!"); 
   } 
  } 
 }

在这个例子中,在主线程中调用thread.join(); 就是将主线程加入到thread子线程后面等待执行。不过有时间限制,为1毫秒。

4、线程的优先级

每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。

Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~•0之间,也可以使用Thread类提供的三个静态常量

  • MAX_PRIORITY   =10

  • MIN_PRIORITY   =1

  • NORM_PRIORITY   =5

例子:


public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   new MyThread("高级", 10).start(); 
   new MyThread("低级", 1).start(); 
  } 
 }  
 class MyThread extends Thread { 
  public MyThread(String name,int pro) { 
   super(name);//设置线程的名称 
   setPriority(pro);//设置线程的优先级 
  } 
  @Override 
  public void run() { 
   for (int i = 0; i < 100; i++) { 
    System.out.println(this.getName() + "线程第" + i + "次执行!"); 
   } 
  } 
 }

从结果可以看到 ,一般情况下,高级线程更显执行完毕。

注意一点:虽然Java提供了10个优先级别,但这些优先级别需要操作系统的支持。不同的操作系统的优先级并不相同,而且也不能很好的和Java的10个优先级别对应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才能保证程序最好的可移植性。

5、守护线程

守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。 

守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。 

setDaemon方法的详细说明:

public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。  

该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。  

  参数:

    on - 如果为 true,则将该线程标记为守护线程。       

  抛出:   

    IllegalThreadStateException - 如果该线程处于活动状态。     

    SecurityException - 如果当前线程无法修改该线程。


 /** 
 * Java线程:线程的调度-守护线程 
 */ 
 public class Test { 
   public static void main(String[] args) { 
     Thread t1 = new MyCommon(); 
     Thread t2 = new Thread(new MyDaemon()); 
     t2.setDaemon(true);  //设置为守护线程 
     t2.start(); 
     t1.start(); 
   } 
 } 
 class MyCommon extends Thread { 
   public void run() { 
     for (int i = 0; i < 5; i++) { 
       System.out.println("线程1第" + i + "次执行!"); 
       try { 
         Thread.sleep(7); 
       } catch (InterruptedException e) { 
         e.printStackTrace(); 
       } 
     } 
   } 
 }  
 class MyDaemon implements Runnable { 
   public void run() { 
     for (long i = 0; i < 9999999L; i++) { 
      System.out.println("后台线程第" + i + "次执行!"); 
       try { 
         Thread.sleep(7); 
       } catch (InterruptedException e) { 
         e.printStackTrace(); 
       } 
     } 
   } 
 }

执行结果:

1. 后台线程第0次执行! 
2. 线程1第0次执行! 
3. 线程1第1次执行! 
4. 后台线程第1次执行! 
5. 后台线程第2次执行! 
6. 线程1第2次执行! 
7. 线程1第3次执行! 
8. 后台线程第3次执行! 
9. 线程1第4次执行! 
10. 后台线程第4次执行! 
11. 后台线程第5次执行! 
12. 后台线程第6次执行! 
13. 后台线程第7次执行!

从上面的执行结果可以看出:前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。 

实际上:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。

守护线程的用途:

守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。Java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了。

6、如何结束一个线程

Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程运行的方法已经被废弃了,使用它们是极端不安全的!想要安全有效的结束一个线程,可以使用下面的方法。

1、正常执行完run方法,然后结束掉

2、控制循环条件和判断条件的标识符来结束掉线程

比如说run方法这样写:


 class MyThread extends Thread { 
  int i=0; 
  @Override 
  public void run() { 
   while (true) { 
    if(i==10) 
     break; 
    i++; 
    System.out.println(i); 
    
   } 
  } 
 }

或者


 class MyThread extends Thread { 
  int i=0; 
  boolean next=true; 
  @Override 
  public void run() { 
   while (next) { 
    if(i==10) 
     next=false; 
    i++; 
    System.out.println(i); 
   } 
  } 
 }

或者


 class MyThread extends Thread { 
  int i=0; 
  @Override 
  public void run() { 
   while (true) { 
    if(i==10) 
     return; 
    i++; 
    System.out.println(i); 
   } 
  } 
 }

只要保证在一定的情况下,run方法能够执行完毕即可。而不是while(true)的无线循环。

3、使用interrupt结束一个线程。

诚然,使用第2中方法的标识符来结束一个线程,是一个不错的方法,但是如果,该线程是处于sleep、wait、join的状态的时候,while循环就不会执行,那么我们的标识符就无用武之地了,当然也不能再通过它来结束处于这3种状态的线程了。

可以使用interrupt这个巧妙的方式结束掉这个线程。

我们看看sleep、wait、join方法的声明:


 public final void wait() throws InterruptedException 
 public static native void sleep(long millis) throws InterruptedException 
 public final void join() throws InterruptedException

可以看到,这三者有一个共同点,都抛出了一个InterruptedException的异常。

在什么时候会产生这样一个异常呢?

每个Thread都有一个中断状状态,默认为false。可以通过Thread对象的isInterrupted()方法来判断该线程的中断状态。可以通过Thread对象的interrupt()方法将中断状态设置为true。

当一个线程处于sleep、wait、join这三种状态之一的时候,如果此时他的中断状态为true,那么它就会抛出一个InterruptedException的异常,并将中断状态重新设置为false。

看下面的简单的例子:


 public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   MyThread thread=new MyThread(); 
   thread.start(); 
  } 
 }  
 class MyThread extends Thread { 
  int i=1; 
  @Override 
  public void run() { 
   while (true) { 
    System.out.println(i); 
   System.out.println(this.isInterrupted()); 
    try { 
     System.out.println("我马上去sleep了"); 
    Thread.sleep(2000); 
     this.interrupt(); 
    } catch (InterruptedException e) { 
     System.out.println("异常捕获了"+this.isInterrupted()); 
     return; 
    } 
    i++; 
   } 
  } 
 }

测试结果:

1. 1 
2. false 
3. 我马上去sleep了 
4. 2 
5. true 
6. 我马上去sleep了 
7. 异常捕获了false 

可以看到,首先执行第一次while循环,在第一次循环中,睡眠2秒,然后将中断状态设置为true。当进入到第二次循环的时候,中断状态就是第一次设置的true,当它再次进入sleep的时候,马上就抛出了InterruptedException异常,然后被我们捕获了。然后中断状态又被重新自动设置为false了(从最后一条输出可以看出来)。

所以,我们可以使用interrupt方法结束一个线程。具体使用如下:


 public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   MyThread thread=new MyThread(); 
   thread.start(); 
   Thread.sleep(3000); 
   thread.interrupt(); 
 } 
 } 
 
 class MyThread extends Thread { 
  int i=0; 
  @Override 
  public void run() { 
   while (true) { 
   System.out.println(i); 
    try { 
     Thread.sleep(1000); 
    } catch (InterruptedException e) { 
    System.out.println("中断异常被捕获了"); 
     return; 
    } 
    i++; 
  } 
  } 
 }

多测试几次,会发现一般有两种执行结果:

 0 
1  
 2 

4. 中断异常被捕获了

或者

 0  
 1 


5. 中断异常被捕获了 

这两种结果恰恰说明了  只要一个线程的中断状态一旦为true,只要它进入sleep等状态,或者处于sleep状态,立马回抛出InterruptedException异常。

第一种情况,是当主线程从3秒睡眠状态醒来之后,调用了子线程的interrupt方法,此时子线程正处于sleep状态,立马抛出InterruptedException异常。

La première situation est lorsque le thread principal se réveille de l'état de veille de 3 secondes et appelle la méthode d'interruption du sous-thread. À ce moment, le sous-thread n'est pas encore en état de veille. Puis, au cours de la troisième boucle while, il entre en état de veille et lève immédiatement une InterruptedException.

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:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn