【2023】Bases du multithread Java et classes de threads communs

1. Concepts de base des processus et des threads

1. Le contexte du processus

L'ordinateur d'origine ne pouvait accepter que certaines instructions spécifiques, et chaque fois que l'utilisateur saisissait une instruction, l'ordinateur effectuait une opération. Pendant que l'utilisateur réfléchit ou tape, l'ordinateur attend. Ceci est très inefficace et, dans de nombreux cas, l'ordinateur est en attente.

Système d'exploitation par lots

Plus tard, il y avait un système d'exploitation par lots, qui écrivait une série d'instructions à utiliser, formait une liste et la transmettait à l'ordinateur en une seule fois. L'utilisateur écrit plusieurs programmes qui doivent être exécutés sur une bande, puis laisse l'ordinateur lire et exécuter ces programmes un par un et écrire les résultats de sortie sur une autre bande.

Le système d'exploitation par lots améliore dans une certaine mesure l'efficacité de l'ordinateur, mais comme le mode d'exécution des instructions du système d'exploitation par lots est toujours en série, il n'y a toujours qu'un seul programme en cours d'exécution dans la mémoire et les programmes suivants doivent attendre l'exécution. du programme précédent à terminer. Avant que l'exécution puisse commencer, le programme précédent est parfois bloqué en raison d'opérations d'E/S, de réseau et d'autres raisons, de sorte que l'efficacité du traitement par lots n'est pas élevée.

Proposition de processus

Les gens ont des exigences de plus en plus élevées en matière de performances informatiques. Le système d'exploitation par lots existant ne peut pas répondre aux besoins des gens. Le goulot d'étranglement du système d'exploitation par lots est qu'il n'y a qu'un seul programme dans la mémoire. Alors peut-il y avoir plusieurs programmes dans la mémoire ? C’est un problème que les gens doivent résoudre de toute urgence.

C’est pourquoi les scientifiques ont proposé le concept de processus.

Un processus est l'espace alloué en mémoire par le programme d'application, c'est-à-dire le programme en cours d'exécution, et chaque processus n'interfère pas les uns avec les autres. Dans le même temps, le processus enregistre l'état du programme en cours d'exécution à chaque instant.

Programme : une collection de codes écrits dans un certain langage de programmation (java, python, etc.) qui peuvent accomplir une certaine tâche ou fonction. Il s'agit d'une collection ordonnée d'instructions et de données, et c'est un morceau de code statique.

A ce moment, le CPU utilise la rotation des tranches de temps pour exécuter le processus : le CPU alloue une période de temps à chaque processus, appelée sa tranche de temps. Si le processus est toujours en cours d'exécution à la fin de la tranche de temps, l'exécution du processus est suspendue et le CPU est alloué à un autre processus (ce processus est appelé changement de contexte). Si le processus se bloque ou se termine avant la fin de la tranche de temps, le CPU commute immédiatement sans attendre la fin de la tranche de temps.

Lorsqu'un processus est suspendu, il enregistre l'état du processus en cours (identité du processus, ressources utilisées par le processus, etc.), le restaure en fonction de l'état précédemment enregistré lors du prochain retour, puis poursuit l'exécution.

Un système d'exploitation qui utilise la méthode de rotation de tranche de temps processus + CPU ressemble macroscopiquement à l'exécution de plusieurs tâches dans la même période de temps. En d'autres termes, le processus rend possible la simultanéité du système d'exploitation. Bien que la simultanéité implique l'exécution de plusieurs tâches d'un point de vue macro, en fait, pour un processeur monocœur, une seule tâche occupe les ressources du processeur à un moment donné.

Nouvelle augmentation des exigences du système d'exploitation

Bien que l'émergence des processus ait considérablement amélioré les performances du système d'exploitation, au fil du temps, les gens ne sont pas convaincus qu'un processus ne peut faire qu'une seule chose à la fois. Si un processus comporte plusieurs sous-tâches, elles ne peuvent être exécutées qu'une par une. 1. Ces sous-tâches affectent grandement l’efficacité.

Par exemple, lorsqu'un logiciel antivirus détecte l'ordinateur d'un utilisateur, s'il reste bloqué sur un certain test, les éléments de test suivants seront également affectés. En d'autres termes, lorsque vous utilisez la fonction d'analyse antivirus du logiciel antivirus, vous ne pouvez pas utiliser la fonction de nettoyage des ordures du logiciel antivirus avant que l'analyse antivirus ne soit terminée, ce qui ne peut évidemment pas répondre aux exigences des utilisateurs.

Proposition de discussion

Alors ces sous-tâches peuvent-elles être exécutées en même temps ? Les gens ont donc proposé le concept de threads, permettant à un thread d'effectuer une sous-tâche, de sorte qu'un processus contienne plusieurs threads, chaque thread étant responsable d'une sous-tâche distincte.

Après avoir utilisé les threads, les choses deviennent beaucoup plus simples. Lorsque l'utilisateur utilise la fonction d'analyse antivirus, laissez le thread d'analyse antivirus s'exécuter. Dans le même temps, si l'utilisateur utilise la fonction de nettoyage des ordures, le fil d'analyse antivirus peut d'abord être mis en pause, répondre à l'opération de nettoyage des ordures de l'utilisateur et laisser le fil de nettoyage des ordures s'exécuter. Revenez en arrière après avoir répondu, puis exécutez le fil d'analyse antivirus.

Remarque : La façon dont le système d'exploitation alloue des tranches de temps à chaque thread implique des stratégies de planification des threads. Les étudiants intéressés peuvent lire "Système d'exploitation". Cet article ne fournira pas d'explication approfondie.

En bref, l'introduction de processus et de threads a grandement amélioré les performances du système d'exploitation. Les processus rendent possible la concurrence du système d'exploitation, et les threads rendent possible la concurrence interne des processus.

La concurrence peut également être obtenue grâce au multi-traitement. Pourquoi utilisons-nous le multi-threading ?

Le mode multi-processus peut effectivement atteindre la concurrence, mais l'utilisation du multi-thread présente les avantages suivants :

  • La communication entre les processus est plus complexe, tandis que la communication entre les threads est relativement simple. Habituellement, nous devons utiliser des ressources partagées, qui sont plus faciles à communiquer entre les threads.
    Les processus sont lourds, tandis que les threads sont légers, de sorte que la surcharge système du multithreading est moindre.

La différence entre processus et thread

Un processus est un environnement d'exécution indépendant et un thread est une tâche exécutée dans un processus. La différence essentielle entre eux est de savoir s'ils occupent un espace d'adressage mémoire distinct et d'autres ressources système (telles que les E/S) :

  • Les processus occupent individuellement un certain espace d'adressage mémoire, il y a donc une isolation de la mémoire entre les processus, les données sont séparées, le partage des données est complexe mais la synchronisation est simple et chaque processus n'interfère pas les uns avec les autres ; les threads partagent l'espace d'adressage mémoire et les ressources occupées par le processus auquel ils appartiennent et le partage des données est simple, mais la synchronisation est complexe.

  • Un processus occupe à lui seul un certain espace d'adressage mémoire. Les problèmes avec un processus n'affecteront pas les autres processus ni la stabilité du programme principal, et la fiabilité est élevée ; le crash d'un thread peut affecter la stabilité de l'ensemble du programme, et la fiabilité est faible.

  • Un processus occupe à lui seul un certain espace d'adressage mémoire. La création et la destruction d'un processus nécessitent non seulement de sauvegarder les registres et les informations de la pile, mais nécessitent également l'allocation et le recyclage des ressources ainsi que la planification des pages, ce qui est relativement coûteux. Les threads n'ont besoin que de sauvegarder les registres et la pile. informations, qui sont relativement peu nombreuses.

Une autre différence importante est que le processus est l'unité de base d'allocation de ressources par le système d'exploitation, tandis que le thread est l'unité de base de planification par le système d'exploitation, c'est-à-dire l'unité de temps d'allocation du processeur.

2. Changement de contexte de titre

La commutation de contexte (parfois appelée commutation de processus ou commutation de tâches) fait référence au passage du processeur d'un processus (ou thread) à un autre processus (ou thread). Le contexte fait référence au contenu des registres du processeur et du compteur de programme à un moment donné.

Les registres sont une petite quantité de mémoire flash rapide à l'intérieur du processeur. Ils stockent et accèdent généralement aux valeurs intermédiaires dans le processus de calcul pour améliorer la vitesse d'exécution des programmes informatiques.

Le compteur de programme est un registre spécial utilisé pour indiquer
la position dans la séquence d'instructions que le CPU exécute. La valeur stockée est la position de l'instruction en cours d'exécution ou la position de la prochaine instruction à exécuter. La mise en œuvre spécifique dépend de la système spécifique.

Exemple de fils A - B

1. Suspendez d’abord le thread A et enregistrez son état dans la CPU en mémoire.

2. Récupérez le contexte du thread B suivant dans la mémoire et restaurez-le dans le registre CPU, puis exécutez le thread B.

3. Lorsque B termine l'exécution, reprenez le thread A en fonction de la position indiquée par le compteur du programme.

Le CPU implémente un mécanisme multithreading en allouant des tranches de temps CPU pour chaque thread. Le processeur exécute les tâches de manière cyclique via l'algorithme d'allocation de tranche de temps. Une fois que la tâche en cours a exécuté une tranche de temps, elle passe à la tâche suivante.

Cependant, l'état de la tâche précédente sera enregistré avant le basculement, afin que l'état de cette tâche puisse être à nouveau chargé la prochaine fois que vous reviendrez à cette tâche. Ainsi, le processus d'une tâche, de la sauvegarde au rechargement, est un changement de contexte.

Le changement de contexte nécessite généralement beaucoup de calculs, ce qui signifie que cette opération consomme beaucoup de temps CPU, donc plus de threads ne sont pas toujours meilleurs . Comment réduire le nombre de changements de contexte dans le système est une question clé pour améliorer les performances multithread.

2. Classe d'entrée et interface multithread Java

Non-retour

Tout d’abord, nous devons avoir une classe « Thread ». JDK fournit la classe Thread et l'interface Runnable pour nous permettre d'implémenter notre propre classe "thread".

  1. Héritez de la classe Thread et remplacez la méthode run ;
  2. Implémentez la méthode run de l'interface Runnable ;
  • Classe de fil de discussion
public class Demo {
    
    
    public static class MyThread extends Thread {
    
    
        @Override
        public void run() {
    
    
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
    
    
        Thread myThread = new MyThread();
        myThread.start();
    }
}

Notez que le thread n’est pas démarré tant que la méthode start() n’est pas appelée !

Après avoir appelé la méthode start() dans le programme, la machine virtuelle crée d'abord un thread pour nous, puis attend que ce thread obtienne la tranche de temps pour la première fois avant d'appeler la méthode run().

Notez que la méthode start() ne peut pas être appelée plusieurs fois. Après avoir appelé la méthode start() pour la première fois, appeler à nouveau la méthode start() lèvera une IllegalThreadStateException.

  • Interface exécutable
public class Demo {
    
    
    public static class MyThread implements Runnable {
    
    
        @Override
        public void run() {
    
    
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
    
    

        new Thread(new MyThread()).start();

        // Java 8 函数式编程,可以省略MyThread类
        new Thread(() -> {
    
    
            System.out.println("Java 8 匿名内部类");
        }).start();
    }
}

Il y a un retour

Callable, Future et FutureTask
De manière générale, nous utilisons Runnable et Thread pour créer un nouveau thread. Mais ils ont un inconvénient, c'est-à-dire que la méthode run n'a aucune valeur de retour. Parfois, nous souhaitons démarrer un thread pour effectuer une tâche et obtenir une valeur de retour une fois la tâche terminée.

JDK fournit l'interface Callable et l'interface Future pour résoudre ce problème pour nous, qui est également le modèle dit « asynchrone ».

  • Interface appelable
// 自定义Callable
class Task implements Callable<Integer>{
    
    
    @Override
    public Integer call() throws Exception {
    
    
        // 模拟计算需要一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]) throws Exception {
    
    
        // 使用
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        // 注意调用get方法会阻塞当前线程,直到得到结果。
        // 所以实际编码中建议使用可以设置超时时间的重载get方法。
        System.out.println(result.get());
    }
}
  • Interface Future
    L'interface Future ne dispose que de quelques méthodes relativement simples :

cancelLa méthode consiste à tenter d'annuler l'exécution d'un thread.

Notez qu'il s'agit d' une tentative d'annulation, mais elle risque d'échouer . L'annulation peut échouer parce que la tâche a peut-être été terminée, annulée ou parce qu'un autre facteur peut ne pas permettre son annulation. booleanLa valeur de retour du type indique « si l'annulation a réussi ». Le paramètre paramBooleanindique s'il faut annuler l'exécution du thread par interruption.

Alors parfois, afin de permettre l'annulation de la tâche, elle est utilisée Callableà la place Runnable. S'il est utilisé à des fins d'annulation Futuremais sans fournir de résultat utilisable, vous pouvez déclarer Future<?>un type formel et renvoyer null comme résultat de la tâche sous-jacente.

- Classe FutureTask

// 自定义Callable,与上面一样
class Task implements Callable<Integer>{
    
    
    @Override
    public Integer call() throws Exception {
    
    
        // 模拟计算需要一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]) throws Exception {
    
    
        // 使用
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        executor.submit(futureTask);
        System.out.println(futureTask.get());
    }
}

Je suppose que tu aimes

Origine blog.csdn.net/weixin_52315708/article/details/131522011
conseillé
Classement