Multithreading - cycle de vie des threads et contrôle d'état

1. Cycle de vie des fils

 Le cycle de vie d'un thread est le processus allant de la création à la mort d'un thread. Concernant le cycle de vie des threads en Java, jetez d'abord un œil à l'image plus classique ci-dessous :

0b06a75702744b73afcf8a9584a284f7.png

Lorsqu'un thread est créé et démarré, il n'entre pas dans l'état d'exécution dès son démarrage, et il ne reste pas non plus dans l'état d'exécution. Dans le cycle de vie d'un thread , il passe par cinq états différents : Nouveau , Prêt , Exécutable , En cours d'exécution , Bloqué et Mort . Surtout lorsqu'un thread est démarré, il ne peut pas toujours "occuper" le processeur pour s'exécuter seul, le processeur doit donc basculer entre plusieurs threads, de sorte que l'état du thread basculera entre l'exécution et le blocage plusieurs fois.

1) Nouvel état ( Nouveau )

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 état nouvellement créé. Le thread dans l'état nouvellement créé possède son propre espace mémoire et entre dans l'état prêt (Runnable) en appelant la méthode start.

2) État prêt ( exécutable )

Le thread à l'état prêt a déjà les conditions d'exécution (c'est-à-dire qu'il est qualifié pour s'exécuter sur le CPU), mais n'a pas encore reçu le droit d'exécution du CPU. Il se trouve dans la "file d'attente des threads prêts", en attente de le système pour lui allouer un CPU. L'état prêt 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. 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.

3) État de fonctionnement ( en cours d'exécution )

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().

L'état d'exécution peut passer à l'état de blocage, à l'état prêt et à l'état de mort.

Si le thread perd des ressources CPU, il passera de l'état d'exécution à l'état prêt et attendra que le système alloue à nouveau des ressources. Vous pouvez également appeler la méthode rendement() sur un thread en cours d'exécution, et elle abandonnera les ressources CPU et redeviendra prête.

4) État bloqué ( bloqué )

Dans certaines circonstances particulières, lorsqu'une personne suspend ou effectue des opérations d'entrée et de sortie, elle abandonne les droits d'exécution du CPU et interrompt temporairement sa propre exécution, entrant ainsi dans un état de blocage. Elle n'aura plus la possibilité d'être appelée par le CPU. jusqu'à ce qu'il entre dans l'état prêt pour entrer dans l'état de fonctionnement.

Selon les différentes raisons de blocage, l'état de blocage peut être divisé en trois types :

1) En attente de blocage : le thread en cours d'exécution exécute la méthode wait(), faisant entrer ce thread dans l'état de blocage en attente.

           Lorsque des méthodes telles que notify() ou notifyAll() sont appelées, le thread entrera à nouveau dans l'état prêt.

2) Blocage synchrone : si le thread ne parvient pas à acquérir le verrou de synchronisation (car le verrou est occupé par d'autres threads), il entrera dans l'état de blocage de synchronisation.

Lorsque le verrou de synchronisation est acquis avec succès, le thread reviendra à l'état prêt.

3) Autre blocage : lorsque le thread sleep() ou join() est appelé ou qu'une requête d'E/S est émise, le thread entrera dans l'état de blocage.

           Lorsque l'état sleep() expire, join() attend que le thread se termine ou expire, ou que le traitement des E/S est terminé, le thread entre à nouveau dans l'état prêt.

5) État mort ( mort )

Lorsque le thread termine d'exécuter la méthode run() ou quitte la méthode run() en raison d'une exception, le thread termine son cycle de vie. De plus, si le thread exécute la méthode interrompu() ou stop(), il entrera également dans l'état mort avec une sortie anormale.

2. Contrôle de l'état du fil

Méthodes fournies en Java pour contrôler l'état des threads :

Méthodes de contrôle direct : start(), interrompu(), join(), sleep(), rendement()

Méthodes de contrôle indirect : setDaemon(), setPriority()

2.1 Sommeil du fil

Si nous devons mettre le thread en cours d'exécution en pause pendant un certain temps et entrer dans l'état de blocage. Après le temps spécifié, l'état de blocage est débloqué et entre dans l'état prêt, nous pouvons appeler la méthode sleep de Thread. Depuis l'API, on voit qu'il existe deux méthodes sleep : Forme surchargée, mais utilisée exactement de la même manière.

21e7a119b96e44c1b6831fd3afa4d633.png

Par exemple, nous voulons mettre le thread principal en veille toutes les 1 000 millisecondes, puis imprimer le numéro :

[Exemple] Imprimer un nombre toutes les 1 000 millisecondes

Test de classe publique {

    public static void main (String[] arguments) {

        pour ( int je = 1; je < 10; je++) {

            Système. sur .println(i);

            essayez {

                Thread.sleep ( 1000 ); // Met en veille le thread principal pendant 1 seconde

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

}

En exécutant le code, on voit bien que les nombres imprimés sont légèrement espacés dans le temps.

Précautions:

        1) sleep est une méthode statique. Il est préférable de ne pas l'appeler avec un objet instance de Thread, car elle met toujours en veille le thread en cours d'exécution, pas l'objet thread qui l'appelle. Elle n'est valable que pour l'objet thread en cours d'exécution. État.

        2) 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, et nous ne peut pas interférer avec précision. Ainsi, si Thread.sleep(1000) est appelé pour mettre le thread en veille pendant 1 seconde, le résultat peut être supérieur à 1 seconde.

2.2 Priorité du fil

Chaque thread a un attribut de priorité lors de l'exécution. Les threads avec une priorité élevée peuvent obtenir plus d'opportunités d'exécution, tandis que les threads avec une priorité faible peuvent obtenir moins d'opportunités d'exécution. Semblable à la veille des threads, la priorité du thread ne peut toujours pas garantir l'ordre d'exécution du thread. Cependant, les threads avec une priorité élevée ont une plus grande probabilité d'obtenir des ressources CPU, et les threads avec une priorité faible n'ont aucune chance de s'exécuter.

La classe Thread fournit les méthodes setPriority(int newPriority) et getPriority() pour définir et renvoyer la priorité d'un thread spécifié. Le paramètre de la méthode setPriority est un entier, qui peut être défini sur un entier compris entre [1-10]. plus la valeur est grande, puis plus la priorité est élevée, vous pouvez également utiliser les trois constantes statiques fournies par la classe Thread :

public final static int MIN_PRIORITY = 1;

public final static int NORM_PRIORITY = 5;

public final static int MAX_PRIORITY = 10 ;

[Exemple] Tester l'exécution de la priorité du thread

/**

 * Classe de fil personnalisée

 */

la classe TestThread étend le fil {

    public TestThread() {}

    public TestThread (nom de la chaîne, int pro) {

        super (nom); //Définir le nom du fil

        this .setPriority(pro); // Définir la priorité du thread

    }

    @Passer outre

    public void run() {

        pour ( int je = 0; je < 10; je++) {

            System.out .println ( this .getName() + "thread" + i + "thread exécution !");

        }

    }

}

/**

 * Classe d'essai

 */

Test de classe publique {

    public static void main (String[] arguments) {

        new TestThread("Avancé", 10).start();

        new TestThread("bas-niveau", 1).start();

    }

}

En exécutant le programme, vous pouvez voir à partir des résultats que dans des circonstances normales, le thread de haut niveau termine l'exécution en premier.

2.3 Concession de fil

yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出CPU资源给其它的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执行。

实际上,当某个线程调用了yield()方法暂停之后,优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程更有可能获得执行的机会,当然,只是有可能,因为我们不可能精确的干涉CPU调度线程。

【示例】线程让步的使用

/**

 * 自定义线程类

 */

class TestThread extends Thread {

    public TestThread() {}

    public TestThread(String name, int pro) {

        super(name); // 设置线程名字

        this.setPriority(pro); // 设置线程的优先级

    }

    @Override

    public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println(this.getName() + "线程第" + i + "次执行!");

            Thread.yield(); // 线程让步

        }

    }

}

/**

 * 测试类

 */

public class Test {

    public static void main(String[] args) {

        new TestThread("高级", 10).start();

        new TestThread("低级", 1).start();

    }

}

关于sleep()方法和yield()方的区别如下:

  1. sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后,是直接进入就绪状态,所以有可能刚进入就绪状态,又被调度到运行状态。
  2. sleep方法声明抛出了InterruptedException,所以调用sleep方法的时候要捕获该异常,或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。
  1. sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法来控制并发线程的执行。

2.4 线程合并

线程的合并就是:线程A在运行期间,可以调用线程B的join()方法,这样线程A就必须等待线程B执行完毕后,才能继续执行。

应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能,注意,它不是静态方法。

线程合并有三个重载的方法:

12871d6b948a401aa82fd398f9ffa871.png

【示例】线程合并的使用

/**

 * 自定义线程类

 */

class TestThread extends Thread {

    public TestThread() {}

    public TestThread(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 + "次执行!");

        }

    }

}

/**

 * 测试类

 */

public class Test {

    public static void main(String[] args) {

        TestThread th = new TestThread();

        th.start();

        try {

            // 等待th线程执行任务完毕之后,再执行主线程中的任务

            th.join();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        // 主线程任务

        for (int i = 0; i < 30; i++) {

            String name = Thread.currentThread().getName();

            System.out.println(name + "线程第" + i + "次执行!");

        }

    }

}

在这个例子中,在主线程中调用th.join(); 就是将主线程加入到th子线程后面等待执行。

2.5 守护线程

守护线程与普通线程写法上基本没啥区别,调用线程对象的方法setDaemon(true),就可以把该线程标记为守护线程。

当普通线程(前台线程)都全部执行完毕,也就是当前在运行的线程都是守护线程时,Java虚拟机(JVM)将退出。另外,setDaemon(true)方法必须在启动线程前调用,否则抛出IllegalThreadStateException异常。

【示例】线程合并的使用

// 前台线程

class CommonThread extends Thread {

    @Override

    public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println("前台线程第" + i + "次执行!");

        }

    }

}

// 后台线程或守护线程

class DaemonThread extends Thread {

    int i = 0;

    @Override

    public void run() {

        while(true) {

            System.out.println("后台线程第" + i++ + "次执行!");

        }

    }

}

//测试类

Test de classe publique {

    public static void main (String[] arguments) {

        CommonThread ct = nouveau CommonThread();

        DaemonThread dt = new DaemonThread();

        ct.start();

        dt.setDaemon( true ); // Définir dt comme thread démon

        dt.start();

    }

}

Exécutez le code ci-dessus et vous pourrez voir sur les résultats de l'exécution : le thread de premier plan est assuré de terminer l'exécution et le thread d'arrière-plan se termine avant de terminer l'exécution.

Le but du thread démon :

Les threads démons sont généralement utilisés pour effectuer certaines tâches en arrière-plan, telles que la lecture de musique de fond lorsque votre application est en cours d'exécution, la vérification automatique de la grammaire, la sauvegarde automatique, etc. dans un éditeur de texte. Le garbage collection de Java est également un thread démon. L'avantage de garder la ligne est que vous n'avez pas à vous soucier de sa fin . Par exemple, si vous souhaitez jouer de la musique de fond pendant que votre application est en cours d'exécution, si le thread qui joue la musique de fond est défini comme un thread non-démon, alors lorsque l'utilisateur demande à quitter, non seulement le thread principal doit se fermer, mais également la musique de fond doit être notifiée pour être jouée. Le thread se ferme ; s'il est défini comme thread démon, il n'est pas nécessaire.

 

 

Je suppose que tu aimes

Origine blog.csdn.net/shengshanlaolin_/article/details/127476522
conseillé
Classement