CodeGym /Blog Java /Random-FR /Mieux ensemble : Java et la classe Thread. Partie II — Sy...
John Squirrels
Niveau 41
San Francisco

Mieux ensemble : Java et la classe Thread. Partie II — Synchronisation

Publié dans le groupe Random-FR

Introduction

Donc, nous savons que Java a des threads. Vous pouvez lire à ce sujet dans la revue intitulée Mieux ensemble : Java et la classe Thread. Partie I — Threads d'exécution . Les threads sont nécessaires pour effectuer un travail en parallèle. Cela rend très probable que les threads interagiront d'une manière ou d'une autre les uns avec les autres. Voyons comment cela se passe et quels outils de base nous avons. Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 1

Rendement

Thread.yield() est déconcertant et rarement utilisé. Il est décrit de différentes manières sur Internet. Y compris certaines personnes écrivant qu'il existe une file d'attente de threads, dans laquelle un thread descendra en fonction des priorités des threads. D'autres personnes écrivent qu'un thread changera son statut de "Running" à "Runnable" (même s'il n'y a pas de distinction entre ces statuts, c'est-à-dire que Java ne les distingue pas). La réalité est que tout cela est beaucoup moins connu et pourtant plus simple dans un sens. Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 2Un bogue ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) est enregistré pour la yield()documentation de la méthode. Si vous le lisez, il est clair que leyield()La méthode fournit en fait uniquement une recommandation au planificateur de threads Java selon laquelle ce thread peut recevoir moins de temps d'exécution. Mais ce qui se passe réellement, c'est-à-dire si l'ordonnanceur agit sur la recommandation et ce qu'il fait en général, dépend de l'implémentation de la JVM et du système d'exploitation. Et cela peut aussi dépendre d'autres facteurs. Toute la confusion est probablement due au fait que le multithreading a été repensé au fur et à mesure que le langage Java s'est développé. En savoir plus dans l'aperçu ici: Brève introduction à Java Thread.yield() .

Dormir

Un thread peut s'endormir pendant son exécution. C'est le type d'interaction le plus simple avec d'autres threads. Le système d'exploitation qui exécute la machine virtuelle Java qui exécute notre code Java possède son propre planificateur de threads . Il décide quel thread démarrer et quand. Un programmeur ne peut pas interagir avec ce planificateur directement à partir du code Java, uniquement via la JVM. Il ou elle peut demander au planificateur de suspendre le fil pendant un certain temps, c'est-à-dire de le mettre en veille. Vous pouvez en savoir plus dans ces articles : Thread.sleep() et How Multithreading works . Vous pouvez également vérifier le fonctionnement des threads dans les systèmes d'exploitation Windows : Les composants internes de Windows Thread . Et maintenant, voyons-le de nos propres yeux. Enregistrez le code suivant dans un fichier nommé HelloWorldApp.java:

class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Comme vous pouvez le voir, nous avons une tâche qui attend 60 secondes, après quoi le programme se termine. Nous compilons en utilisant la commande " javac HelloWorldApp.java" puis exécutons le programme en utilisant " java HelloWorldApp". Il est préférable de démarrer le programme dans une fenêtre séparée. Par exemple, sous Windows, c'est comme ceci : start java HelloWorldApp. On utilise la commande jps pour obtenir le PID (process ID), et on ouvre la liste des threads avec " jvisualvm --openpid pid: Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 3Comme vous pouvez le voir, notre thread a maintenant le statut "Sleeping". En fait, il existe une façon plus élégante d'aider notre fil fais de beaux rêves:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Avez-vous remarqué que nous manipulons InterruptedExceptionpartout? Comprenons pourquoi.

Thread.interrupt()

Le fait est que pendant qu'un thread attend / dort, quelqu'un peut vouloir l'interrompre. Dans ce cas, nous manipulons un fichier InterruptedException. Ce mécanisme a été créé après que la Thread.stop()méthode ait été déclarée obsolète, c'est-à-dire obsolète et indésirable. La raison en était que lorsque la stop()méthode était appelée, le thread était simplement "tué", ce qui était très imprévisible. Nous ne pouvions pas savoir quand le thread serait arrêté et nous ne pouvions pas garantir la cohérence des données. Imaginez que vous écriviez des données dans un fichier alors que le thread est tué. Plutôt que de tuer le thread, les créateurs de Java ont décidé qu'il serait plus logique de lui dire qu'il devait être interrompu. Comment répondre à cette information est une question pour le fil lui-même de décider. Pour plus de détails, lisez Pourquoi Thread.stop est obsolète ?sur le site Web d'Oracle. Regardons un exemple :

public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Dans cet exemple, nous n'attendrons pas 60 secondes. Au lieu de cela, nous afficherons immédiatement "Interrompu". C'est parce que nous avons appelé la interrupt()méthode sur le thread. Cette méthode définit un indicateur interne appelé "état d'interruption". C'est-à-dire que chaque thread a un indicateur interne qui n'est pas directement accessible. Mais nous avons des méthodes natives pour interagir avec ce drapeau. Mais ce n'est pas le seul moyen. Un thread peut être en cours d'exécution, ne pas attendre quelque chose, effectuer simplement des actions. Mais il peut anticiper que d'autres voudront terminer ses travaux à un moment précis. Par exemple:

public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			// Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Dans l'exemple ci-dessus, la whileboucle sera exécutée jusqu'à ce que le thread soit interrompu en externe. En ce qui concerne le isInterrupteddrapeau, il est important de savoir que si nous attrapons un InterruptedException, le drapeau isInterrupted est réinitialisé, puis isInterrupted()renverra false. La classe Thread possède également une méthode statique Thread.interrupted() qui s'applique uniquement au thread en cours, mais cette méthode réinitialise l'indicateur à false ! Pour en savoir plus, consultez ce chapitre intitulé Interruption de thread .

Rejoindre (attendre qu'un autre fil se termine)

Le type d'attente le plus simple est d'attendre qu'un autre thread se termine.

public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
Dans cet exemple, le nouveau thread dormira 5 secondes. En même temps, le thread principal attendra que le thread endormi se réveille et termine son travail. Si vous regardez l'état du thread dans JVisualVM, il ressemblera à ceci : Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 4Grâce aux outils de surveillance, vous pouvez voir ce qui se passe avec le thread. La joinméthode est assez simple, car il s'agit simplement d'une méthode avec du code Java qui s'exécute wait()tant que le thread sur lequel elle est appelée est actif. Dès que le thread meurt (quand il a fini son travail), l'attente est interrompue. Et c'est toute la magie de la join()méthode. Passons donc au plus intéressant.

Moniteur

Le multithreading inclut le concept de moniteur. Le mot moniteur vient en anglais par le biais du latin du XVIe siècle et signifie "un instrument ou un appareil utilisé pour observer, vérifier ou conserver un enregistrement continu d'un processus". Dans le cadre de cet article, nous allons essayer de couvrir les bases. Pour tous ceux qui veulent les détails, veuillez plonger dans les documents liés. Nous commençons notre voyage avec la spécification du langage Java (JLS) : 17.1. Synchronisation . Il dit ce qui suit : Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 5Il s'avère que Java utilise un mécanisme de "moniteur" pour la synchronisation entre les threads. Un moniteur est associé à chaque objet, et les threads peuvent l'acquérir avec lock()ou le libérer avec unlock(). Ensuite, nous retrouverons le tutoriel sur le site d'Oracle : Intrinsic Locks and Synchronization. Ce didacticiel explique que la synchronisation de Java est construite autour d'une entité interne appelée verrou intrinsèque ou verrou de moniteur . Ce verrou est souvent simplement appelé « moniteur ». Nous voyons également à nouveau que chaque objet en Java est associé à un verrou intrinsèque. Vous pouvez lire Java - Intrinsic Locks and Synchronization . Ensuite, il sera important de comprendre comment un objet en Java peut être associé à un moniteur. En Java, chaque objet a un en-tête qui stocke des métadonnées internes non disponibles pour le programmeur à partir du code, mais dont la machine virtuelle a besoin pour fonctionner correctement avec les objets. L'en-tête de l'objet inclut un "mot de marque", qui ressemble à ceci : Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Voici un article de JavaWorld qui est très utile : Comment la machine virtuelle Java effectue la synchronisation des threads . Cet article doit être combiné avec la description de la section "Résumé" du problème suivant dans le système de suivi des bogues JDK : JDK-8183909 . Vous pouvez lire la même chose ici : JEP-8183909 . Ainsi, en Java, un moniteur est associé à un objet et est utilisé pour bloquer un thread lorsque le thread tente d'acquérir (ou d'obtenir) le verrou. Voici l'exemple le plus simple :

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Ici, le thread en cours (celui sur lequel ces lignes de code sont exécutées) utilise le synchronizedmot clé pour tenter d'utiliser le moniteur associé auobject"\variable pour obtenir/acquérir le verrou. Si personne d'autre ne se bat pour le moniteur (c'est-à-dire que personne d'autre n'exécute de code synchronisé en utilisant le même objet), alors Java peut essayer d'effectuer une optimisation appelée "verrouillage biaisé". Une balise pertinente et un enregistrement indiquant quel thread possède le verrou du moniteur sont ajoutés au mot de marque dans l'en-tête de l'objet. Cela réduit la surcharge nécessaire pour verrouiller un moniteur. Si le moniteur appartenait auparavant à un autre thread, un tel verrouillage ne suffit pas. La JVM passe au type de verrouillage suivant : "verrouillage de base". Il utilise des opérations de comparaison et d'échange (CAS). De plus, le mot de marque de l'en-tête de l'objet lui-même ne stocke plus le mot de marque, mais plutôt une référence à l'endroit où il est stocké, et la balise change pour que la JVM comprenne que nous utilisons le verrouillage de base. Si plusieurs threads sont en concurrence (se disputent) pour un moniteur (un a acquis le verrou et un second attend que le verrou soit libéré), alors la balise dans le mot de marque change et le mot de marque stocke maintenant une référence au moniteur en tant qu'objet - une entité interne de la JVM. Comme indiqué dans la proposition d'amélioration du JDK (JEP), cette situation nécessite de l'espace dans la zone de mémoire du tas natif afin de stocker cette entité. La référence à l'emplacement mémoire de cette entité interne sera stockée dans le mot marque de l'entête de l'objet. Ainsi, un moniteur est en réalité un mécanisme de synchronisation de l'accès aux ressources partagées entre plusieurs threads. La JVM bascule entre plusieurs implémentations de ce mécanisme. Donc, pour simplifier, quand on parle de moniteur, on parle en fait de serrures. et un second attend que le verrou soit libéré), puis la balise dans le mot de marque change, et le mot de marque stocke maintenant une référence au moniteur en tant qu'objet - une entité interne de la JVM. Comme indiqué dans la proposition d'amélioration du JDK (JEP), cette situation nécessite de l'espace dans la zone de mémoire du tas natif afin de stocker cette entité. La référence à l'emplacement mémoire de cette entité interne sera stockée dans le mot marque de l'entête de l'objet. Ainsi, un moniteur est en réalité un mécanisme de synchronisation de l'accès aux ressources partagées entre plusieurs threads. La JVM bascule entre plusieurs implémentations de ce mécanisme. Donc, pour simplifier, quand on parle de moniteur, on parle en fait de serrures. et un second attend que le verrou soit libéré), puis la balise dans le mot de marque change, et le mot de marque stocke maintenant une référence au moniteur en tant qu'objet - une entité interne de la JVM. Comme indiqué dans la proposition d'amélioration du JDK (JEP), cette situation nécessite de l'espace dans la zone de mémoire du tas natif afin de stocker cette entité. La référence à l'emplacement mémoire de cette entité interne sera stockée dans le mot marque de l'entête de l'objet. Ainsi, un moniteur est en réalité un mécanisme de synchronisation de l'accès aux ressources partagées entre plusieurs threads. La JVM bascule entre plusieurs implémentations de ce mécanisme. Donc, pour simplifier, quand on parle de moniteur, on parle en fait de serrures. et le mot de marque stocke maintenant une référence au moniteur en tant qu'objet - une entité interne de la JVM. Comme indiqué dans la proposition d'amélioration du JDK (JEP), cette situation nécessite de l'espace dans la zone de mémoire du tas natif afin de stocker cette entité. La référence à l'emplacement mémoire de cette entité interne sera stockée dans le mot marque de l'entête de l'objet. Ainsi, un moniteur est en réalité un mécanisme de synchronisation de l'accès aux ressources partagées entre plusieurs threads. La JVM bascule entre plusieurs implémentations de ce mécanisme. Donc, pour simplifier, quand on parle de moniteur, on parle en fait de serrures. et le mot de marque stocke maintenant une référence au moniteur en tant qu'objet - une entité interne de la JVM. Comme indiqué dans la proposition d'amélioration du JDK (JEP), cette situation nécessite de l'espace dans la zone de mémoire du tas natif afin de stocker cette entité. La référence à l'emplacement mémoire de cette entité interne sera stockée dans le mot marque de l'entête de l'objet. Ainsi, un moniteur est en réalité un mécanisme de synchronisation de l'accès aux ressources partagées entre plusieurs threads. La JVM bascule entre plusieurs implémentations de ce mécanisme. Donc, pour simplifier, quand on parle de moniteur, on parle en fait de serrures. La référence à l'emplacement mémoire de cette entité interne sera stockée dans le mot marque de l'entête de l'objet. Ainsi, un moniteur est en réalité un mécanisme de synchronisation de l'accès aux ressources partagées entre plusieurs threads. La JVM bascule entre plusieurs implémentations de ce mécanisme. Donc, pour simplifier, quand on parle de moniteur, on parle en fait de serrures. La référence à l'emplacement mémoire de cette entité interne sera stockée dans le mot marque de l'entête de l'objet. Ainsi, un moniteur est en réalité un mécanisme de synchronisation de l'accès aux ressources partagées entre plusieurs threads. La JVM bascule entre plusieurs implémentations de ce mécanisme. Donc, pour simplifier, quand on parle de moniteur, on parle en fait de serrures. Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 7

Synchronisé (en attente d'un verrou)

Comme nous l'avons vu précédemment, la notion de "bloc synchronisé" (ou "section critique") est étroitement liée à la notion de moniteur. Jetez un oeil à un exemple:

public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized(lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized(lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print(" " + i);
		}
		System.out.println(" ...");
	}
}
Ici, le thread principal passe d'abord l'objet de tâche au nouveau thread, puis acquiert immédiatement le verrou et effectue une longue opération avec celui-ci (8 secondes). Pendant tout ce temps, la tâche est incapable de continuer, car elle ne peut pas entrer dans le synchronizedbloc, car le verrou est déjà acquis. Si le thread ne peut pas obtenir le verrou, il attendra le moniteur. Dès qu'il obtiendra le verrou, il poursuivra son exécution. Lorsqu'un thread quitte un moniteur, il libère le verrou. Dans JVisualVM, cela ressemble à ceci : Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 8Comme vous pouvez le voir dans JVisualVM, le statut est "Monitor", ce qui signifie que le thread est bloqué et ne peut pas prendre le moniteur. Vous pouvez également utiliser du code pour déterminer l'état d'un thread, mais les noms d'état déterminés de cette manière ne correspondent pas aux noms utilisés dans JVisualVM, bien qu'ils soient similaires. Dans ce cas, leth1.getState()L'instruction dans la boucle for renverra BLOCKED , car tant que la boucle est en cours d'exécution, le lockmoniteur de l'objet est occupé par le mainthread, et le th1thread est bloqué et ne peut pas continuer tant que le verrou n'est pas libéré. En plus des blocs synchronisés, une méthode entière peut être synchronisée. Par exemple, voici une méthode de la HashTableclasse :

public synchronized int size() {
	return count;
}
Cette méthode sera exécutée par un seul thread à la fois. Avons-nous vraiment besoin de la serrure ? Oui, nous en avons besoin. Dans le cas des méthodes d'instance, l'objet "this" (objet courant) agit comme un verrou. Il y a une discussion intéressante sur ce sujet ici : Y a-t-il un avantage à utiliser une méthode synchronisée au lieu d'un bloc synchronisé ? . Si la méthode est statique, le verrou ne sera pas l'objet "this" (car il n'y a pas d'objet "this" pour une méthode statique), mais plutôt un objet Class (par exemple, ) Integer.class.

Attendez (en attente d'un moniteur). méthodes notify() et notifyAll()

La classe Thread a une autre méthode d'attente associée à un moniteur. Contrairement sleep()à et join(), cette méthode ne peut pas simplement être appelée. Son nom est wait(). La waitméthode est appelée sur l'objet associé au moniteur que l'on veut attendre. Voyons un exemple :

public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
Dans JVisualVM, cela ressemble à ceci : Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 10Pour comprendre comment cela fonctionne, rappelez-vous que les méthodes wait()et notify()sont associées à java.lang.Object. Il peut sembler étrange que des méthodes liées aux threads soient dans la Objectclasse. Mais la raison de cela se dévoile maintenant. Vous vous souviendrez que chaque objet en Java a un en-tête. L'en-tête contient diverses informations de gestion, y compris des informations sur le moniteur, c'est-à-dire l'état du verrou. N'oubliez pas que chaque objet, ou instance d'une classe, est associé à une entité interne de la JVM, appelée verrou ou moniteur intrinsèque. Dans l'exemple ci-dessus, le code de l'objet tâche indique que nous entrons dans le bloc synchronisé du moniteur associé à l' lockobjet. Si nous réussissons à acquérir le verrou pour ce moniteur, alorswait()est appelé. Le thread exécutant la tâche libérera le lockmoniteur de l'objet, mais entrera dans la file d'attente des threads en attente de notification du lockmoniteur de l'objet. Cette file d'attente de threads est appelée WAIT SET, ce qui reflète plus correctement son objectif. C'est-à-dire qu'il s'agit plus d'un ensemble que d'une file d'attente. Le mainthread crée un nouveau thread avec l'objet tâche, le démarre et attend 3 secondes. Cela rend très probable que le nouveau thread puisse acquérir le verrou avant le mainthread et entrer dans la file d'attente du moniteur. Après cela, le mainthread lui-même entre dans le lockbloc synchronisé de l'objet et effectue une notification de thread à l'aide du moniteur. Une fois la notification envoyée, le mainthread libère lelockmoniteur de l'objet, et le nouveau thread, qui attendait auparavant lockla libération du moniteur de l'objet, continue son exécution. Il est possible d'envoyer une notification à un seul thread ( notify()) ou simultanément à tous les threads de la file d'attente ( notifyAll()). En savoir plus ici : Différence entre notify() et notifyAll() en Java . Il est important de noter que l'ordre de notification dépend de la manière dont la JVM est implémentée. En savoir plus ici : Comment résoudre la famine avec notifier et notifier tout ? . La synchronisation peut être effectuée sans spécifier d'objet. Vous pouvez le faire lorsqu'une méthode entière est synchronisée plutôt qu'un seul bloc de code. Par exemple, pour les méthodes statiques, le verrou sera un objet Class (obtenu via .class) :

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
En termes d'utilisation des verrous, les deux méthodes sont les mêmes. Si une méthode n'est pas statique, la synchronisation sera effectuée en utilisant le courant instance, c'est-à-dire en utilisant this. Au fait, nous avons dit plus tôt que vous pouvez utiliser la getState()méthode pour obtenir le statut d'un thread. Par exemple, pour un thread dans la file d'attente en attente d'un moniteur, le statut sera WAITING ou TIMED_WAITING, si la wait()méthode a spécifié un délai d'attente. Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

Cycle de vie des threads

Au cours de sa vie, le statut d'un thread change. En fait, ces changements comprennent le cycle de vie du thread. Dès qu'un thread est créé, son statut est NEW. Dans cet état, le nouveau thread n'est pas encore en cours d'exécution et le planificateur de threads Java n'en sait encore rien. Pour que le planificateur de thread puisse en savoir plus sur le thread, vous devez appeler la thread.start()méthode. Ensuite, le thread passera à l'état RUNNABLE. Internet contient de nombreux schémas incorrects qui font la distinction entre les états "Runnable" et "Running". Mais c'est une erreur, car Java ne fait pas la distinction entre "prêt à fonctionner" (exécutable) et "fonctionnant" (en cours d'exécution). Lorsqu'un thread est actif mais non actif (non exécutable), il se trouve dans l'un des deux états suivants :
  • BLOQUÉ — en attente d'entrer dans une section critique, c'est-à-dire un synchronizedblocage.
  • WAITING — attend qu'un autre thread satisfasse à une condition.
Si la condition est satisfaite, le planificateur de thread démarre le thread. Si le thread attend jusqu'à une heure spécifiée, son statut est TIMED_WAITING. Si le thread n'est plus en cours d'exécution (il est terminé ou une exception a été levée), il passe alors à l'état TERMINATED. Pour connaître l'état d'un thread, utilisez la getState()méthode. Les threads ont également une isAlive()méthode qui renvoie true si le thread n'est pas TERMINATED.

LockSupport et parking de fil

À partir de Java 1.6, un mécanisme intéressant appelé LockSupport est apparu. Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 12Cette classe associe un "permis" à chaque thread qui l'utilise. Un appel à la park()méthode revient immédiatement si le permis est disponible, consommant le permis dans le processus. Sinon, ça bloque. L'appel de la unparkméthode rend le permis disponible s'il n'est pas encore disponible. Il n'y a qu'un seul permis. La documentation Java pour LockSupportfait référence à la Semaphoreclasse. Prenons un exemple simple :

import java.util.concurrent.Semaphore;
public class HelloWorldApp{
    
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Ce code attendra toujours, car maintenant le sémaphore a 0 permis. Et quand acquire()est appelé dans le code (c'est-à-dire demander le permis), le thread attend jusqu'à ce qu'il reçoive le permis. Puisque nous attendons, nous devons gérer InterruptedException. Fait intéressant, le sémaphore obtient un état de thread séparé. Si nous regardons dans JVisualVM, nous verrons que l'état n'est pas "Wait", mais "Park". Mieux ensemble : Java et la classe Thread.  Partie II — Synchronisation - 13Prenons un autre exemple :

public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());
        
        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
Le statut du thread sera WAITING, mais JVisualVM fait la distinction entre waitle synchronizedmot-clé et parkla LockSupportclasse. Pourquoi est-ce LockSupportsi important ? Nous nous tournons à nouveau vers la documentation Java et examinons l' état du thread WAITING . Comme vous pouvez le voir, il n'y a que trois façons d'y entrer. Deux de ces façons sont wait()et join(). Et le troisième est LockSupport. En Java, les verrous peuvent également être construits sur LockSupport et offrir des outils de niveau supérieur. Essayons d'en utiliser un. Par exemple, jetez un œil à ReentrantLock:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
Tout comme dans les exemples précédents, tout est simple ici. L' lockobjet attend que quelqu'un libère la ressource partagée. Si nous regardons dans JVisualVM, nous verrons que le nouveau thread sera parqué jusqu'à ce que le mainthread libère le verrou. Vous pouvez en savoir plus sur les verrous ici : Java 8 StampedLocks vs. ReadWriteLocks et Synchronized and Lock API in Java. Pour mieux comprendre comment les verrous sont implémentés, il est utile de lire sur Phaser dans cet article : Guide to the Java Phaser . Et en parlant de divers synchroniseurs, vous devez lire l' article de DZone sur les synchroniseurs Java.

Conclusion

Dans cette revue, nous avons examiné les principales façons dont les threads interagissent en Java. Matériels supplémentaires: Mieux ensemble : Java et la classe Thread. Partie I — Threads d'exécution Mieux ensemble : Java et la classe Thread. Partie III — Interaction Mieux ensemble : Java et la classe Thread. Partie IV — Callable, Future et friends Mieux ensemble : Java et la classe Thread. Partie V — Executor, ThreadPool, Fork/Join Mieux ensemble : Java et la classe Thread. Partie VI — Tirez !
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION