Maison  >  Article  >  Java  >  Analyse détaillée des méthodes de programmation multithread en JAVA (avec exemples)

Analyse détaillée des méthodes de programmation multithread en JAVA (avec exemples)

王林
王林avant
2019-08-30 11:46:251979parcourir

1. Programme, processus, fil

Un programme est un ensemble ordonné d'instructions, qui peut également être compris de manière populaire comme plusieurs lignes de code. Il n'a aucune signification en soi. Il s'agit simplement d'une entité statique. Il peut s'agir d'un simple fichier texte ou d'un fichier exécutable généré après la compilation.
 Au sens étroit, un processus est une instance d'un programme en cours d'exécution ; au sens large, un processus est une activité en cours d'exécution d'un programme avec certaines fonctions indépendantes sur une certaine collection de données. Le processus est l'unité de base d'allocation des ressources par le système d'exploitation.
Un thread est la plus petite unité d'un processus qui peut être exécutée indépendamment. C'est également l'unité de base pour la planification et la répartition indépendantes par le processeur. Un processus peut contenir plusieurs threads, chaque thread effectue ses propres tâches et tous les threads du même processus partagent des ressources dans le processus, telles que l'espace mémoire, les descripteurs de fichiers, etc.

2. Introduction à la programmation multithread

1. Qu'est-ce que la programmation multithread

Plus La technologie de programmation Thread est une fonctionnalité importante du langage Java. Le sens de la programmation multithread est de diviser les tâches du programme en plusieurs sous-tâches parallèles et d'attribuer ces sous-tâches à plusieurs threads pour exécution.
 La programmation multithread est un paradigme de programmation avec les threads comme unité abstraite de base. Cependant, la programmation multithread n'est pas aussi simple que l'utilisation de plusieurs threads pour la programmation, mais elle comporte également ses propres problèmes qui doivent être résolus. La programmation multithread et la programmation orientée objet sont compatibles, c'est-à-dire que nous pouvons implémenter une programmation multithread basée sur la programmation orientée objet. En fait, un thread dans la plateforme Java est un objet.

2. Pourquoi utiliser la programmation multithread ?

Les ordinateurs d'aujourd'hui ont souvent des cœurs multiprocesseurs, et chaque thread ne peut fonctionner que sur un seul processeur à la fois. supérieur. Si un seul thread est utilisé pour le développement, les ressources du processeur multicœur ne peuvent pas être pleinement utilisées pour améliorer l'efficacité d'exécution du programme. Lors de l'utilisation du multithreading pour la programmation, différents threads peuvent s'exécuter sur différents processeurs. De cette manière, non seulement l'utilisation des ressources informatiques est grandement améliorée, mais également l'efficacité d'exécution du programme est améliorée.

3. Introduction à l'API JAVA Thread

La classe java.lang.Thread est l'implémentation des threads sur la plateforme Java. Une instance de la classe Thread ou de sa sous-classe est un thread.

1. Création, démarrage et exécution de threads

Dans la plateforme Java, la création d'un thread est un exemple de création d'une classe Thread (ou de sa sous-classe). Chaque thread a une tâche à accomplir. La logique de traitement des tâches du thread peut être directement implémentée dans la méthode run de la classe Thread ou appelée via cette méthode. Par conséquent, la méthode run est équivalente à la méthode d'entrée de la logique de traitement des tâches du thread. Elle doit être directement appelée par le virtuel Java. machine lors de l’exécution du thread correspondant. Il ne doit pas être appelé par le code de l’application.
L'exécution d'un thread permet en fait à la machine virtuelle Java d'exécuter la méthode d'exécution du thread, afin que le code logique de traitement des tâches puisse être exécuté. Si un thread n'est pas démarré, sa méthode run ne sera jamais exécutée. Pour ce faire, vous devez d'abord démarrer le fil de discussion. La méthode start de la classe Thread permet de démarrer le thread correspondant. L'essence du démarrage d'un thread est de demander à la machine virtuelle d'exécuter le thread correspondant, et le moment où ce thread peut s'exécuter est déterminé par le planificateur de threads (le planificateur de threads fait partie du système d'exploitation). Par conséquent, appeler la méthode start d'un thread ne signifie pas que le thread a commencé à s'exécuter. Le thread peut commencer à s'exécuter immédiatement, peut être exécuté plus tard ou ne jamais s'exécuter.
Deux façons de créer des fils de discussion sont présentées ci-dessous (en fait, il existe d'autres méthodes, qui seront présentées en détail dans les articles suivants). Avant cela, jetons un œil au code source de la méthode run de la classe Thread :

// Code 1-1@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

Cette méthode d'exécution est définie dans l'interface Runnable. Elle n'accepte pas de paramètres et n'a aucune valeur de retour. En fait, il n'y a qu'une seule méthode dans l'interface Runnable, cette interface est donc une interface fonctionnelle, ce qui signifie que nous pouvons utiliser des expressions lambda là où Runnable est nécessaire. La classe Thread implémente cette interface, elle doit donc implémenter cette méthode. target est un champ de la classe Thread et son type est également Runnable. Le champ cible indique ce que ce thread doit exécuter, et la méthode run de la classe Thread exécute uniquement la méthode run de la cible.
Nous venons de mentionner que la machine virtuelle Java appelle automatiquement la méthode run du thread. Cependant, la méthode run de la classe Thread a été définie et nous n'avons aucun moyen de mettre le code que nous devons exécuter dans la méthode run de la classe Thread. Par conséquent, nous pouvons envisager d’autres moyens d’affecter le comportement de la méthode run. La première consiste à hériter de la classe Thread et à remplacer la méthode run, de sorte que la JVM appelle notre méthode run remplacée au lieu de la méthode run de la classe Thread lors de l'exécution du thread ; à Thread La méthode cible de la classe, et la classe Thread a plusieurs constructeurs qui peuvent directement attribuer des valeurs à la cible. De cette façon, la JVM exécute toujours le code que nous avons transmis lors de l'appel de la méthode run.
 Dans la plateforme Java, chaque thread peut avoir son propre nom par défaut. Bien entendu, nous pouvons également donner un nom à notre thread lors de la construction d'une instance de la classe Thread. Ce nom nous permet de distinguer plus facilement les différents threads.
 Le code suivant utilise les deux méthodes ci-dessus pour créer deux fils de discussion. La tâche qu'ils doivent effectuer est très simple : imprimer une ligne de message de bienvenue et inclure leurs propres noms.

public class WelcomeApp {
    public static void main(String[] args) {
        Thread thread1 = new WelcomeThread();
        Thread thread2 = new Thread(() -> System.out.println("2. Welcome, I'm " + Thread.currentThread().getName()));
        thread1.start();
        thread2.start();
    }
}class WelcomeThread extends Thread {
    @Override
    public void run() {
        System.out.println("1. Welcome, I'm " + Thread.currentThread().getName());
    }
}

Ce qui suit est le résultat de ce programme lorsqu'il est exécuté :

1. Welcome, I'm Thread-0
2. Welcome, I'm Thread-1

Exécutez ce programme plusieurs fois, nous pouvons constater que le résultat de ce programme peut également être :

2. Welcome, I'm Thread-1
1. Welcome, I'm Thread-0

Cela montre que, bien que thread1 soit démarré avant thread2, cela ne signifie pas que thread1 sera exécuté avant thread2.
Quelle que soit la méthode utilisée pour créer un thread, une fois l'exécution de la méthode run du thread (appelée par la JVM) terminée, l'exécution du thread correspondant se terminera également. Bien entendu, la fin de l'exécution de la méthode run inclut la fin normale (la méthode run renvoie normalement) et la fin provoquée par des exceptions levées dans le code. Les ressources (telles que l'espace mémoire) occupées par le thread dont l'exécution est terminée seront recyclées par la JVM comme les autres objets Java.
Les threads sont des "éléments jetables". Nous ne pouvons pas réexécuter un thread qui a fini de s'exécuter en rappelant la méthode start. En fait, la méthode start ne peut être appelée qu'une seule fois. Appeler plusieurs fois la méthode start de la même instance de Thread entraînera la levée d'une exception IllegalThreadStateException.

2. Attributs du fil

Les attributs du fil incluent le numéro du fil, le nom, la catégorie et la priorité, les détails sont affichés dans le tableau suivant :

Analyse détaillée des méthodes de programmation multithread en JAVA (avec exemples)

Les concepts de threads démons et de threads utilisateur sont mentionnés ci-dessus. En voici une brève explication. Selon que le thread empêchera la machine virtuelle Java de s'arrêter normalement, nous pouvons diviser les threads en Java en threads démons (Daemon Thread) et threads utilisateur (User Thread, également appelés threads non démons). L'attribut démon du thread est utilisé pour indiquer si le thread correspondant est un thread démon. Les threads utilisateur empêcheront la machine virtuelle Java de s'arrêter normalement. Autrement dit, une machine virtuelle Java ne peut s'arrêter normalement que lorsque tous ses threads utilisateur ont fini de s'exécuter (c'est-à-dire que l'appel Thread.run() n'est pas terminé). Le thread démon n'affectera pas l'arrêt normal de la machine virtuelle Java. Même si le thread démon est en cours d'exécution dans l'application, cela n'affectera pas l'arrêt normal de la machine virtuelle Java. Par conséquent, les threads démons sont généralement utilisés pour effectuer des tâches qui ne sont pas très importantes, telles que la surveillance de l'état d'exécution d'autres threads.
Bien sûr, si la machine virtuelle Java est forcée de s'arrêter, par exemple en utilisant la commande kill pour terminer de force un processus de machine virtuelle Java sous le système Linux, alors même le thread utilisateur ne peut pas empêcher la machine virtuelle Java de s'arrêter.

3. Méthodes courantes de la classe Thread

Analyse détaillée des méthodes de programmation multithread en JAVA (avec exemples)

Tout morceau de code en Java est toujours exécuté dans un certain thread. Le thread exécutant le code actuel est appelé le thread actuel et Thread.currentThread() peut renvoyer le thread actuel. Étant donné que le même morceau de code peut être exécuté par différents threads, le thread actuel est relatif, c'est-à-dire que la valeur de retour de Thread.currentThread() peut correspondre à différents threads (objets) lorsque le code est réellement en cours d'exécution.
La méthode join est équivalente au thread exécutant la méthode et le planificateur de thread dit : "Je dois d'abord faire une pause et attendre que l'autre thread ait fini de s'exécuter avant de pouvoir continuer."
La méthode statique de rendement est équivalente à. exécution Le thread de cette méthode dit au planificateur de threads : "Je ne suis pas pressé maintenant. Si quelqu'un d'autre a besoin des ressources du processeur, laissez-le l'utiliser en premier. Bien sûr, si personne d'autre ne veut l'utiliser, je ne Cela ne me dérange pas de continuer à l'occuper. ."

4、Thread类中的废弃方法

Analyse détaillée des méthodes de programmation multithread en JAVA (avec exemples)

虽然这些方法并没有相应的替代品,但是可以使用其他办法来实现,我们会在后续文章中学习这部分内容。

四、无处不在的线程

Java平台本身就是一个多线程的平台。除了Java开发人员自己创建和使用的线程,Java平台中其他由Java虚拟机创建、使用的线程也随处可见。当然,这些线程也是各自有其处理任务。
  Java虚拟机启动的时候会创建一个主线程(main线程),该线程负责执行Java程序的入口方法(main方法)。下面的程序打印出主线程的名称:

public class MainThreadDemo {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

  该程序会输出“main”,这说明main方法是由一个名为“main”的线程调用的,这个线程就是主线程,它是由JVM创建并启动的。
  在多线程编程中,弄清楚一段代码具体是由哪个(或者哪种)线程去负责执行的这点很重要,这关系到性能、线程安全等问题。本系列的后续文章会体现这点。
  Java 虚拟机垃圾回收器(Garbage Collector)负责对Java程序中不再使用的内存空间进行回收,而这个回收的动作实际上也是通过专门的线程(垃圾回收线程)实现的,这些线程由Java虚拟机自行创建。
  为了提高Java代码的执行效率,Java虚拟机中的JIT(Just In Time)编译器会动态地将Java字节码编译为Java虚拟机宿主机处理器可直接执行的机器码。这个动态编译的过程实际上是由Java虚拟机创建的专门的线程负责执行的。
  Java平台中的线程随处可见,这些线程各自都有其处理任务。

五、线程的层次关系

Java平台中的线程不是孤立的,线程与线程之间总是存在一些联系。假设线程A所执行的代码创建了线程B, 那么,习惯上我们称线程B为线程A的子线程,相应地线程A就被称为线程B的父线程。例如, Code 1-2中的线程thread1和thread2是main线程的子线程,main线程是它们的父线程。子线程所执行的代码还可以创建其他线程,因此一个子线程也可以是其他线程的父线程。所以,父线程、子线程是一个相对的称呼。理解线程的层次关系有助于我们理解Java应用程序的结构,也有助于我们后续阐述其他概念。
  在Java平台中,一个线程是否是一个守护线程默认取决于其父线程:默认情况下父线程是守护线程,则子线程也是守护线程;父线程是用户线程,则子线程也是用户线程。另外,父线程在创建子线程后启动子线程之前可以调用该线程的setDaemon方法,将相应的线程设置为守护线程(或者用户线程)。
  一个线程的优先级默认值为该线程的父线程的优先级,即如果我们没有设置或者更改一个线程的优先级,那么这个线程的优先级的值与父线程的优先级的值相等。
  不过,Java平台中并没有API用于获取一个线程的父线程,或者获取一个线程的所有子线程。并且,父线程和子线程之间的生命周期也没有必然的联系。比如父线程运行结束后,子线程可以继续运行,子线程运行结束也不妨碍其父线程继续运行。

六、线程的生命周期状态

 在Java平台中,一个线程从其创建、启动到其运行结束的整个生命周期可能经历若干状态。如下图所示:

Analyse détaillée des méthodes de programmation multithread en JAVA (avec exemples)

Le statut du thread peut être obtenu en appelant Thread.getState(). Le type de valeur de retour de Thread.getState() est Thread.State, qui est un type d'énumération à l'intérieur de la classe Thread. Les états de thread définis par Thread.State sont les suivants :
à NEW : un thread qui a été créé mais non démarré est dans cet état. Puisqu'une instance de thread ne peut être démarrée qu'une seule fois, un thread ne peut être dans cet état qu'une seule fois.
RUNNABLE : Cet état peut être considéré comme un état composite, qui comprend deux sous-états : READY et RUNNING, mais en fait ces deux états ne sont pas définis dans Thread.State. Le premier signifie que le thread dans cet état peut être planifié par le planificateur de thread pour le mettre dans l'état RUNNING. Ce dernier indique que le thread dans cet état est en cours d'exécution, c'est-à-dire que les instructions correspondant à la méthode d'exécution de l'objet thread correspondant sont en cours d'exécution par le processeur. L'état du thread exécutant Thread.yield() peut être converti de RUNNING à READY. Les threads du sous-état READY sont également appelés threads actifs.
BLOCKED : après qu'un thread ait lancé une opération d'E/S bloquante ou demandé une ressource exclusive (telle qu'un verrou) détenue par un autre thread, le thread correspondant sera dans cet état. Les threads à l’état BLOQUÉ n’occupent pas de ressources processeur. Lorsque l'opération de blocage 1/0 est terminée ou que le thread obtient les ressources qu'il a demandées, l'état du thread peut être converti en RUNNABLE.
WAITING : Après qu'un thread ait exécuté certaines méthodes spécifiques, il sera dans cet état en attendant que d'autres threads effectuent d'autres opérations spécifiques. Les méthodes qui peuvent faire passer son thread d'exécution à l'état WAITING incluent : Object.wait(), Thread.join() et LockSupport.park(Object). Les méthodes correspondantes qui peuvent changer le thread correspondant de WAITING à RUNNABLE incluent : Object.notify()/notifyAll() et LockSupport.unpark(Object)).
TIMED_WAITING : Cet état est similaire à WAITING. La différence est que le thread dans cet état n'attend pas indéfiniment que d'autres threads effectuent des opérations spécifiques, mais est dans un état d'attente avec une limite de temps. Lorsque d'autres threads n'effectuent pas les opérations spécifiques attendues par le thread dans le délai spécifié, l'état du thread est automatiquement converti en RUNNABLE.
TERMINATED : Le thread qui a terminé son exécution est dans cet état. Puisqu’une instance de thread ne peut être démarrée qu’une seule fois, un thread ne peut être dans cet état qu’une seule fois. La méthode run retourne normalement ou se termine prématurément en raison du lancement d'une exception, ce qui entraînera le thread correspondant dans cet état.
Un thread ne peut être dans l'état NEW et l'état TERMINATED qu'une seule fois dans tout son cycle de vie.

7. Avantages de la programmation multithread

La programmation multithread présente les avantages suivants :

Améliorer l'efficacité du système Débit : la programmation multithread permet plusieurs opérations concurrentes (c'est-à-dire simultanées) en un seul processus. Par exemple, pendant qu'un thread attend une opération d'E/S, d'autres threads peuvent toujours effectuer leurs opérations.

Amélioration de la réactivité : lors de l'utilisation d'une programmation multithread, pour les logiciels GUI (tels que les applications de bureau), une opération lente (comme le téléchargement d'un fichier volumineux depuis le serveur) ne provoquera pas l'interface du logiciel être « gelé » et ne peut pas répondre aux opérations des autres utilisateurs ; pour les applications Web, le traitement lent d'une requête n'affectera pas le traitement des autres requêtes.

Exploitez pleinement les ressources des processeurs multicœurs : de nos jours, les appareils dotés de processeurs multicœurs sont de plus en plus populaires, et même les appareils grand public tels que les téléphones mobiles utilisent couramment des processeurs multicœurs. La mise en œuvre d'une programmation multithread appropriée nous aide à utiliser pleinement les ressources du processeur multicœur de l'appareil et à éviter de gaspiller des ressources.

La programmation multithread présente également ses propres problèmes et risques, notamment les aspects suivants :

Problèmes de sécurité des threads. Lorsque plusieurs threads partagent des données, si les mesures de contrôle d'accès simultanées correspondantes ne sont pas prises, des problèmes de cohérence des données peuvent survenir, tels que la lecture de données sales (données expirées) et la perte de mises à jour (les mises à jour effectuées par certains threads sont supprimées par d'autres threads). les threads écrasent), etc.

Problème d'activité du fil de discussion. L’ensemble du cycle de vie d’un thread depuis sa création jusqu’à la fin de son exécution va passer par différents états. Du point de vue d'un seul thread, l'état RUNNABLE est l'état souhaité. Mais en fait, une mauvaise écriture de code peut amener certains threads à attendre que d'autres threads libèrent le verrou (état BLOCKED). Cette situation est appelée un blocage (Deadlock). Bien entendu, des problèmes peuvent également survenir avec un thread qui est toujours occupé. Il peut être confronté à un problème de livelock, c'est-à-dire qu'un thread a tenté une opération mais ne peut pas progresser. De plus, les threads constituent une ressource informatique rare et le nombre de processeurs dont dispose un système est toujours très petit par rapport au nombre de threads présents dans le système. Dans certains cas, le problème de la famine des threads (Starvation) peut survenir, c'est-à-dire que certains threads ne peuvent jamais avoir la chance d'être exécutés par le processeur et sont toujours dans le sous-état READY de l'état RUNNABLE.

Changement de contexte. Lorsque le processeur passe de l'exécution d'un thread à l'exécution d'un autre thread, une action requise par le système d'exploitation est appelée un changement de contexte. En raison de la rareté des ressources du processeur, le changement de contexte peut être considéré comme un sous-produit inévitable de la programmation multithread. Il augmente la consommation du système et n'est pas propice au débit du système.

Pour plus de questions connexes, veuillez visiter le site Web PHP chinois : Tutoriel vidéo JAVA

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