1. Que sont les processus et les threads
1.1 Signification
1.1.1 Processus
Un processus est une instance d'un programme en cours d'exécution. Dans un système d'exploitation, un processus représente un programme en cours d'exécution, qui comprend le code du programme, les données et les ressources système nécessaires à l'exécution du programme.
Le plus intuitif est notre gestionnaire de tâches :
Chaque application dans le Gestionnaire des tâches est un processus.
1.1.2 Fils
Un thread est une unité d'exécution dans un processus. Un processus peut contenir plusieurs threads, chaque thread a un chemin d'exécution indépendant et peut effectuer indépendamment des tâches spécifiques, et tous les threads d'un processus partagent les ressources de ce processus.
Comme l'exemple suivant. Processus : L'ensemble du processus d'exploitation d'un restaurant peut être considéré comme un processus. Il comprend toutes les ressources et activités telles que la cuisine, la salle de restaurant, les serveurs, les cuisiniers, les clients, etc. Fil : Dans un restaurant, un serveur peut être considéré comme un fil. Il y a de nombreux serveurs, qui sont chargés de recevoir les clients, d'enregistrer les commandes, de passer les menus, de servir les plats et d'autres tâches. Le serveur est une unité d'exécution dans le processus de restauration, et il partage les mêmes ressources avec d'autres serveurs, comme la table, la cuisine, les ingrédients, etc. du restaurant. Plusieurs serveurs peuvent exécuter des tâches en parallèle pour améliorer l'efficacité.
1.1.3 La différence entre thread et processus
La naissance du processus consiste à réaliser l'exécution concurrente du programme. La soi-disant simultanéité signifie que dans le système d'exploitation, il y a plusieurs programmes dans une période de temps entre le démarrage et l'exécution et l'exécution, et ces programmes s'exécutent tous sur le même processeur.
Le but de la naissance des threads est de réduire la surcharge de temps et d'espace des programmes lors de l'exécution simultanée, afin que le système d'exploitation ait une meilleure simultanéité.
- Espace d'adressage : les threads partagent l'espace d'adressage du processus, tandis que les processus ont des espaces d'adressage indépendants.
- Ressources : les threads partagent les ressources du processus, telles que la mémoire, les E/S, le processeur, etc., tandis que les ressources entre les processus sont indépendantes.
- Robustesse : le multi-processus est plus fort que le multi-thread. Après qu'un processus plante, il n'affectera pas les autres processus en mode protégé, mais si un thread plante, tout le processus mourra.
- Processus d'exécution : chaque processus indépendant a une entrée de programme en cours d'exécution, une séquence d'exécution séquentielle et une sortie de programme, et la surcharge d'exécution est élevée. Cependant, les threads ne peuvent pas être exécutés indépendamment et doivent dépendre du programme d'application.Le programme d'application fournit un contrôle d'exécution de plusieurs threads et la surcharge d'exécution est faible.
- Concurrence : Les deux peuvent être exécutés simultanément.
- Lors de la commutation : Lorsque le processus est commuté, il consomme beaucoup de ressources et a une faible efficacité. Ainsi, lorsqu'il s'agit de commutations fréquentes, il est préférable d'utiliser des threads plutôt que des processus.
- Autres : Le processus est l'unité de base de l'allocation des ressources du système d'exploitation et le thread est l'unité de base de la planification du système d'exploitation .
1.2 PCB et TCB
Process control block PCB (Process Control Block), qui est une structure de données utilisée pour décrire et contrôler le fonctionnement du processus . C'est le seul signe de l'existence du processus, et c'est aussi une base importante pour que le système d'exploitation gère le processus.
TCB (Thread Control Block) est l'abréviation de Thread Control Block, qui est une structure de données utilisée dans le système d'exploitation pour décrire et contrôler le fonctionnement des threads .
Leur relation:
Les identifiants de processus, les statuts, les priorités, les registres, les compteurs de programme, les pointeurs de pile, les listes de ressources, les mécanismes de synchronisation et de communication et d'autres informations sont stockés dans le PCB. Le PCB est le seul signe de l'existence d'un processus, et c'est également la base pour réaliser la commutation et l'ordonnancement des processus.
Le TCB est similaire au PCB. Le TCB stocke également les identifiants de thread, l'état, la priorité, les registres, les compteurs de programme, les pointeurs de pile, le blindage du signal et d'autres informations. TCB est le seul signe de l'existence d'un thread, et c'est également la base de la commutation et de la planification des threads.
2. Plusieurs façons de créer des threads
Thread est un concept du système d'exploitation. Le noyau du système d'exploitation implémente un mécanisme tel que les threads et fournit certaines API à la couche utilisateur que les utilisateurs peuvent utiliser. La classe Thread de la bibliothèque standard Java peut être considérée comme une autre abstraction et encapsulation de l'API fournie par le système d'exploitation.
2.1 Héritage de la classe Thread
(1) Hériter Thread pour créer une classe de thread.
class MyThread extends Thread{
//重写run方法,run 表示这个线程需要执行的任务
@Override
public void run() {
System.out.println("这个线程需要执行的任务");
}
}
(2) Créez une instance de MyThread.
//创建一个 MyThread 实例
MyThread myThread = new MyThread();
(3) Appelez la méthode start() et le thread commence à s'exécuter.
//start 方法表示这个线程开始执行,注意,这里不是调用 run()方法
myThread.start();
2.2 Implémenter l'interface Runnable
(1) Implémenter l'interface Runnable.
//通过 实现 Runnable 接口来创建一个线程
class MyRunnable implements Runnable{
//需要重写run方法
@Override
public void run() {
System.out.println("这个线程需要执行的任务");
}
}
(2) Créez une instance de thread.
//创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为参数。
Thread thread = new Thread(new MyRunnable());
(3) Appelez la méthode start() et le thread commence à s'exécuter.
//线程开始运行
thread.start();
2.3 Autres déformations
- Une classe interne anonyme crée un objet de sous-classe Thread.
Thread thread1 = new Thread(){
@Override
public void run() {
System.out.println("使用匿名类创建 Thread 子类对象");
}
};
- Les classes internes anonymes créent des objets de sous-classe Runnable.
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名类创建 Runnable 子类对象");
}
});
- Les expressions lambda créent des objets de sous-classe Runnable.
Thread thread3 = new Thread(()-> {
System.out.println("使用匿名类创建 Thread 子类对象");
});
3. Méthodes communes de la classe Thread
3.1 Méthodes de construction courantes de Thread
méthode | illustrer |
Fil de discussion() | créer un objet thread |
Thread (cible exécutable) | Créer un objet thread à l'aide d'un objet Runnable |
Thread (nom de la chaîne) | Créez un objet thread et nommez-le |
Thread (cible exécutable, nom de chaîne) | Utilisez l'objet Runnable pour créer un objet thread et nommez-le |
Thread thread = new Thread("小明"){
@Override
public void run(){
while(true){
System.out.println("我是小明");
}
}
};
Le nom de ce fil est "Xiao Ming", comment vérifier le nom de ce fil ? Dans le kit de développement Java (JDK), vous pouvez utiliser jconsole pour surveiller les threads.
- Ouvrez votre chemin jdk, voici C:\Program Files\Java\jdk1.8.0_31\bin, ouvrez l'outil jconsole sous ce chemin.
- Exécutez le code ci-dessus, puis procédez comme suit :
Vous pouvez voir qu'il existe un fil nommé "Xiao Ming", qui est le fil que nous avons créé.
3.2 Méthode pour obtenir l'attribut Thread
Les attributs | Méthode d'accès |
IDENTIFIANT | getId() |
nom | obtenirNom() |
État | getState() |
priorité | getPriorité() |
Que ce soit le fil d'arrière-plan | estDémon() |
s'il faut survivre | est vivant() |
Est-il interrompu | est interrompu() |
- ID est l'identifiant unique du thread, différents threads ne seront pas répétés.
- name est le nom du thread.
- L'état représente une situation dans laquelle se trouve actuellement le thread, comme le blocage, l'exécution, etc.
- Les threads avec une priorité plus élevée sont plus susceptibles d'être planifiés.
- À propos du thread d'arrière-plan : Daemon Thread est un thread qui fournit des services en arrière-plan pendant l'exécution du programme. Contrairement aux threads de premier plan (également appelés threads utilisateur), les threads d'arrière-plan n'empêchent pas l'arrêt du programme. Les threads d'arrière-plan sont automatiquement terminés lorsque tous les threads de premier plan (threads utilisateur) se terminent, même s'ils n'ont pas encore fini de s'exécuter.
- Qu'il soit vivant, c'est-à-dire une compréhension simple, c'est si la méthode run est terminée.
3.3 La méthode pour démarrer le thread start()
Cela a été démontré dans l'article précédent, et nous avons vu comment créer un objet thread en redéfinissant la méthode run, mais la création d'un objet thread ne signifie pas que le thread commence à s'exécuter. Remplacez la méthode run pour fournir au thread une liste d'instructions sur ce qu'il faut faire. Ce n'est qu'en appelant la méthode start() que le thread peut s'exécuter indépendamment, et ce n'est qu'en appelant la méthode start qu'un thread peut être créé au bas du système d'exploitation.
3.4 thread sommeil sommeil()
méthode | illustrer |
public static void sleep(long millis) lance InterruptedException | Dormir le thread actuel pendant des millis millisecondes |
public static void sleep(long millis, int nanos) lance InterruptedException | Peut dormir avec une plus grande précision |
Le code suivant :
public static void main(String[] args) throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(3000);//睡眠当前线程
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
résultat:
Certains de ses contenus plus détaillés sont présentés plus tard.
3.5 Interrompre les fils
L'interruption ici ne signifie pas une interruption immédiate. Examinons le cas suivant pour plus de détails.
Il y a deux threads ici, un thread est utilisé pour interrompre l'autre thread et la méthode Thread n'est pas utilisée ici.
public class Main {
public static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run(){
// flag 是中断标记
while(flag){
try {
Thread.sleep(500);//睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hi");
}
}
};
//开启线程
thread.start();
//中断线程
Thread.sleep(3000); //睡眠 3 秒
flag = false;//更改标记
System.out.println("开始中断线程");
}
}
(Le rôle de volatile ne sera pas présenté ici et continuera d'être mis à jour ultérieurement.)
résultat:
Vous constaterez que l'interruption ici consiste à modifier la valeur du bit d'indicateur, et qu'il y a un retard ici, et que le thread ne peut être terminé que lorsque le sleep sleep se termine.
Vous pouvez donc utiliser la méthode fournie par Thread, à savoir : isInterrupted() ou interrupted(), leur avantage est qu'ils peuvent réveiller le thread endormi immédiatement, voir le cas suivant.
méthode | illustrer |
public void interruption() | Interrompre le thread associé à l'objet, si le thread est bloqué, il sera notifié de façon anormale, sinon le bit flag sera positionné |
public statique booléen interrompu() | Déterminez si le bit d'indicateur d'interruption du thread actuel est défini et effacez le bit d'indicateur après avoir appelé |
booléen public isInterrupted() | Déterminez si le bit d'indicateur du thread associé à l'objet est défini et si le bit d'indicateur n'est pas effacé après l'appel |
Les détails des drapeaux ici sont décrits plus tard.
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run(){
// flag 是中断标记
while(!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(500);//随眠
} catch (InterruptedException e) {
e.printStackTrace();//打印报错信息
}
System.out.println("Hi");
}
}
};
//开启线程
thread.start();
Thread.sleep(3000); //睡眠 3 秒
thread.interrupt();//中断 thread 线程,把标记位置为true。
System.out.println("开始中断线程");
}
Expliquez ici ! Thread.currentThread().isInterrupted(), Thread.currentThread() doit renvoyer la référence de l'objet thread actuel, qui ressemble à ceci :
méthode | illustrer |
public static Thread currentThread(); | Renvoie une référence à l'objet thread actuel |
isInterrupted() : renvoie true si le thread en cours est interrompu, sinon renvoie false.
L'interruption ici doit être visualisée en fonction du bit d'indicateur d'interruption.Le bit d'indicateur d'interruption (drapeau d'interruption) est l'un des états internes du thread et existe réellement dans la structure de données interne du thread. **Plus précisément, le bit d'indicateur d'interruption est une variable membre de l'objet thread, qui est utilisée pour indiquer l'état d'interruption du thread. **Ce drapeau est initialisé à false lorsque le thread est créé. Lorsque la méthode interrupt() du thread est appelée, le drapeau d'interruption sera défini sur true, indiquant que le thread est interrompu.
Thread.currentThread().isInterrupted(), indique ici si le thread en cours est interrompu, et renvoie vrai s'il est interrompu, sinon faux. Enfin, ajoutez un ! devant comme condition de while, c'est-à-dire qu'il retournera faux s'il est interrompu, sinon il sera vrai.
Enfin, regardons les résultats.Notez que nous avons appelé thread.interrupt() ci-dessus, donc on s'attend à ce que sleep lève une exception, puis termine la boucle (fil de fin),
résultat:
Pourquoi le programme ne s'arrête-t-il pas ici ? Analysons-le lentement. Il est évident que la condition de la boucle while est vraie si elle n'est pas terminée. Alors la valeur de Thread.currentThread().isInterrupted() est false, ce qui signifie que le thread n'est pas dans un état interrompu . Quoi? N'avons-nous pas déjà appelé la méthode interrupt() ?
La raison est la suivante : lorsque le thread est bloqué, comme l'appel de la méthode sleep (), join () ou wait (), si une demande d'interruption est reçue, ces méthodes lèvent InterruptedException et effacent l'état de l'interruption ! , l'état d'interruption clair ici consiste à définir la position de l'indicateur sur false, indiquant que le thread n'a pas été interrompu.
Comment résoudre ce problème? Ajoutez simplement une pause directement à la prise.
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run(){
// flag 是中断标记
while(!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(500);//随眠
} catch (InterruptedException e) {
//这里进行善后处理
break;
}
System.out.println("Hi");
}
}
};
//开启线程
thread.start();
//中断线程
Thread.sleep(3000); //睡眠 3 秒
thread.interrupt();//中断 thread 线程
System.out.println("开始中断线程");
}
Quels sont les avantages de faire quelque chose comme ça? Le thread peut gérer la demande d'interruption selon sa propre logique, comme terminer la boucle, fermer la ressource ou l'ignorer.
Résumé : conditions préalables, call interrupt();
- Si le thread est bloqué et suspendu en raison de méthodes d'appel telles que wait/join/sleep, il sera notifié sous la forme d'InterruptedException et l'indicateur d'interruption sera réinitialisé. La fin du thread à ce moment dépend de la manière dont le code dans le catch est écrit. Vous pouvez choisir d'ignorer cette exception, ou vous pouvez sauter hors de la boucle pour terminer le thread.
- S'il n'y a pas de méthodes telles que wait/join/sleep,
- Thread.interrupted() Détermine si l'indicateur d'interruption du thread actuel est défini et réinitialise l'indicateur d'interruption après évaluation.
- Thread.currentThread().isInterrupted() Détermine si l'indicateur d'interruption du thread actuel est défini et ne réinitialise pas l'indicateur d'interruption.
3.6 Attente de la jointure du thread ()
Parfois, nous devons attendre qu'un thread termine son travail avant de passer à l'étape suivante.
méthode | décrire |
rejoindre() | Attendez que le fil soit terminé |
joindre (long millis) | Attendez jusqu'au nombre de millisecondes spécifié, si le thread ne termine pas son exécution dans ce délai, le thread actuel continuera à s'exécuter |
join(long millis, int nanos) | Attendez jusqu'au nombre spécifié de millisecondes et de nanosecondes. Si le thread ne termine pas son exécution dans ce délai, le thread actuel continuera à s'exécuter |
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run(){
for (int i = 0; i < 9; i++) {
System.out.println("thread");
}
}
};
thread.start();//开启线程
System.out.println("join()前");
try {
thread.join();//先等 thread 线程执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("join()后");
}
}
Si le thread de thread a fini de s'exécuter avant join(), join() ne bloquera pas.
Ces méthodes join() fournissent un mécanisme d'exécution coopératif entre les threads, permettant à un thread d'attendre qu'un autre thread se termine avant de continuer à s'exécuter. Ceci est utile pour les scénarios qui nécessitent un ordre d'exécution des threads ou des dépendances entre les threads.