-
Étant donné que plusieurs threads du même processus partagent le même espace de stockage, tout en apportant de la commodité, cela pose également de sérieux problèmes de conflits d'accès. Le langage Java fournit un mécanisme spécial pour résoudre ce conflit, empêchant efficacement l'accès au même objet de données par plusieurs threads en même temps.
Puisque nous pouvons utiliser le mot-clé private pour garantir que l'objet de données n'est accessible que par les méthodes, il suffit de proposer un mécanisme pour les méthodes. Ce mécanisme est le mot-clé synchronisé, qui comprend deux utilisations : méthode synchronisée et bloc synchronisé.
1. méthode synchronisée : déclarez la méthode synchronisée en ajoutant le mot-clé synchronisé dans la déclaration de la méthode. comme:
public synchronisé void accessVal (int newVal);
La méthode synchronisée contrôle l'accès aux variables membres de la classe : chaque instance de classe correspond à un verrou. Chaque méthode synchronisée doit obtenir le verrou de l'instance de classe qui appelle la méthode avant de pouvoir être exécutée. Sinon, le thread auquel elle appartient est bloqué. Une fois la méthode exécutée, elle occupera exclusivement la variable membre de la classe. Le verrou n'est libéré qu'au retour de cette méthode. Après cela, le thread bloqué peut obtenir le verrou et rentrer dans l'état exécutable. Ce mécanisme garantit que pour chaque instance de classe à la fois, au plus une de toutes ses fonctions membres déclarées synchronisées est à l'état exécutable (car au plus on peut obtenir le verrou correspondant à l'instance de classe) (faites attention à cette instruction ! Parce qu'il s'agit d'un verrou pour une instance de classe, donc pour un objet à la fois, une seule méthode synchronisée est exécutée par un thread à la fois), évitant ainsi efficacement les conflits d'accès des variables membres de la classe (à condition que toutes les méthodes qui peuvent les variables membres de la classe d'accès sont déclarées comme synchronisées).
En Java, non seulement les instances de classe, chaque classe correspond également à un verrou. De cette façon, nous pouvons également déclarer les fonctions membres statiques de la classe comme synchronisées pour contrôler son accès aux variables membres statiques de la classe.
Défauts de la méthode synchronisée : Si une grande méthode est déclarée synchronisée, l'efficacité sera grandement affectée. Généralement, si la méthode de la classe thread run() est déclarée synchronisée, elle continuera à s'exécuter pendant tout le cycle de vie du thread. . Les appels à toutes les méthodes synchronisées de cette classe ne réussiront jamais. Bien sûr, nous pouvons résoudre ce problème en plaçant le code qui accède aux variables membres de la classe dans une méthode spéciale, en le déclarant synchronisé et en l'appelant dans la méthode principale, mais Java nous offre une meilleure solution, à savoir le bloc synchronisé.
2. bloc synchronisé : déclarez le bloc synchronisé via le mot-clé synchronisé. La syntaxe est la suivante :
synchronisé(syncObject) {
//Code pour autoriser le contrôle d'accès
}
Un bloc synchronisé est un bloc de code dans lequel le code doit obtenir le verrou de l'objet syncObject (comme mentionné précédemment, il peut s'agir d'une instance de classe ou d'une classe) avant de pouvoir être exécuté. Le mécanisme spécifique est le même que celui mentionné ci-dessus. Parce qu'il peut cibler n'importe quel bloc de code et spécifier arbitrairement l'objet verrouillé, il offre une grande flexibilité.
Blocage de thread (synchronisation)
Afin de résoudre les conflits d'accès aux zones de stockage partagées, Java a introduit un mécanisme de synchronisation. Examinons maintenant l'accès aux ressources partagées par plusieurs threads. Évidemment, le mécanisme de synchronisation ne suffit plus, car les ressources requises à tout moment peuvent ne pas l'être. prête Pour être accessible, à son tour, il peut y avoir plus d'une ressource prête en même temps. Pour résoudre le problème du contrôle d'accès dans ce cas, Java a introduit la prise en charge du mécanisme de blocage.
Le blocage fait référence au fait de suspendre l'exécution d'un thread pour attendre qu'une certaine condition se produise (comme une ressource prête). Les étudiants qui ont étudié les systèmes d'exploitation doivent le connaître. Java fournit un grand nombre de méthodes pour prendre en charge le blocage. Analysons-les une par une.
1. Méthode sleep() : sleep() vous permet de spécifier une période de temps en millisecondes comme paramètre. Cela fait que le thread entre dans l'état de blocage dans le délai spécifié et ne peut pas obtenir le temps CPU une fois le temps spécifié écoulé. le thread rentre dans l’état exécutable.
Généralement, sleep() est utilisé lors de l'attente qu'une ressource soit prête : une fois que le test a constaté que la condition n'est pas remplie, laissez le thread se bloquer pendant un certain temps, puis retestez jusqu'à ce que la condition soit remplie.
2. Méthodes suspend() et curriculum vitae() (faciles à provoquer une impasse, obsolètes) : les deux méthodes sont utilisées ensemble. suspend() fait entrer le thread dans un état de blocage et sa reprise() correspondante ne doit pas être automatiquement récupérée. appelé. , le thread peut-il rentrer dans l'état exécutable. Généralement, suspend() et curriculum vitae() sont utilisés lors de l'attente d'un résultat produit par un autre thread : une fois que le test constate que le résultat n'a pas été produit, le thread est bloqué, et après qu'un autre thread ait produit le résultat, appelez curriculum vitae(). pour le reprendre.
3. Méthode rendement() : rendement() oblige le thread à abandonner le temps CPU actuellement alloué, mais ne provoque pas le blocage du thread, c'est-à-dire que le thread est toujours dans un état exécutable et peut se voir allouer à nouveau du temps CPU à à tout moment. L'effet de l'appel de rendement() équivaut à ce que le planificateur considère que le thread a exécuté suffisamment de temps pour passer à un autre thread.
4. Méthodes wait() et notify() : Les deux méthodes sont utilisées ensemble. wait() fait entrer le thread dans un état de blocage. L'une permet de spécifier une période de temps en millisecondes comme paramètre, et l'autre permet de spécifier une période de temps en millisecondes. les autres ne le font pas. Paramètres, le premier entrera à nouveau dans l'état exécutable lorsque le notify() correspondant est appelé ou que le temps spécifié est dépassé, et le second doit être appelé lorsque le notify() correspondant est appelé.
À première vue, elles ne semblent pas différentes des paires de méthodes suspend() et curriculum vitae(), mais en fait elles sont complètement différentes. La principale différence est que toutes les méthodes décrites ci-dessus ne libéreront pas le verrou occupé (s'il est occupé) lors du blocage, alors que cette règle opposée est le contraire.
Les différences fondamentales ci-dessus conduisent à une série de différences détaillées.
Tout d'abord, toutes les méthodes décrites ci-dessus appartiennent à la classe Thread, mais cette paire appartient directement à la classe Object, c'est-à-dire que tous les objets ont cette paire de méthodes. Cela peut sembler incroyable au début, mais en fait c'est très naturel, car lorsque cette paire de méthodes se bloque, le verrou occupé doit être libéré, et le verrou est possédé par n'importe quel objet. L'appel de la méthode wait() de n'importe quel objet provoque le blocage. thread à bloquer et le verrou sur l’objet est libéré. L'appel de la méthode notify() de n'importe quel objet entraînera le déblocage d'un thread sélectionné au hasard bloqué en appelant la méthode wait() de l'objet (mais il ne sera pas exécuté tant que le verrou n'est pas obtenu).
Deuxièmement, toutes les méthodes décrites ci-dessus peuvent être appelées à n'importe quel endroit, mais cette paire de méthodes (wait() et notify()) doit être appelée dans une méthode ou un bloc synchronisé. La raison est également très simple, uniquement dans une méthode synchronisée. ou block Seul le thread actuel occupe le verrou et le verrou peut être libéré. De la même manière, le verrou sur l'objet qui appelle cette paire de méthodes doit appartenir au thread actuel, afin que le verrou puisse être libéré. Par conséquent, la paire d’appels de méthode doit être placée dans une méthode ou un bloc synchronisé dont l’objet verrouillé est l’objet qui appelle la paire de méthodes. Si cette condition n'est pas remplie, même si le programme peut toujours se compiler, une exception IllegalMonitorStateException se produira au moment de l'exécution.
Les caractéristiques ci-dessus des méthodes wait() et notify() déterminent qu'elles sont souvent utilisées avec des méthodes ou des blocs synchronisés. Leur comparaison avec le mécanisme de communication inter-processus du système d'exploitation révélera leurs similitudes : les méthodes ou blocs synchronisés fournissent des résultats similaires. aux fonctions des primitives du système d'exploitation, leur exécution ne sera pas perturbée par le mécanisme multi-thread, et cette contrepartie est équivalente aux primitives de bloc et de réveil (cette paire de méthodes sont toutes deux déclarées synchronisées). Leur combinaison nous permet d'implémenter une série d'algorithmes de communication inter-processus exquis sur le système d'exploitation (tels que les algorithmes de sémaphore) et peut être utilisée pour résoudre divers problèmes complexes de communication inter-thread.
Deux derniers points sur les méthodes wait() et notify() :
Premièrement : le thread débloqué provoqué par l'appel de la méthode notify() est sélectionné aléatoirement parmi les threads bloqués par l'appel de la méthode wait() de l'objet. Nous ne pouvons pas prédire quel thread sera sélectionné, donc soyez particulièrement prudent lors de la programmation, pour éviter. problèmes découlant de cette incertitude.
Deuxièmement : en plus de notify(), il existe également une méthode notifyAll() qui peut également jouer un rôle similaire. La seule différence est que l'appel de la méthode notifyAll() supprimera tous les threads bloqués en appelant la méthode wait() de la méthode notify(). objet à la fois. Tous débloqués. Bien entendu, seul le thread qui acquiert le verrou peut entrer dans l’état exécutable.
Lorsque nous parlons de blocage, nous devons parler de blocage. Une brève analyse peut révéler que la méthode suspend() et l'appel de la méthode wait() sans spécifier de délai d'attente peuvent provoquer un blocage. Malheureusement, Java ne permet pas d'éviter les blocages au niveau du langage, et nous devons faire attention à éviter les blocages dans la programmation.
Ci-dessus, nous avons analysé les différentes méthodes de blocage de threads en Java. Nous nous sommes concentrés sur les méthodes wait() et notify() car elles sont les plus puissantes et les plus flexibles à utiliser, mais cela conduit également à leur efficacité et à leur efficacité. est plus sujet aux erreurs. Dans la pratique, nous devons utiliser diverses méthodes avec flexibilité pour mieux atteindre nos objectifs.
fil démon
Les threads démons sont un type spécial de threads. La différence entre eux et les threads ordinaires est qu'ils ne constituent pas la partie centrale de l'application. Lorsque tous les threads non démons d'une application se terminent, même s'il y a encore des threads démons en cours d'exécution, l'application. will Terminate, d'autre part, l'application ne se terminera pas tant qu'un thread non démon sera en cours d'exécution. Les threads démons sont généralement utilisés pour fournir des services à d’autres threads en arrière-plan.
Vous pouvez déterminer si un thread est un thread démon en appelant la méthode isDaemon(), ou vous pouvez appeler la méthode setDaemon() pour définir un thread comme thread démon.
groupe de discussions
Le groupe de threads est un concept unique à Java. En Java, le groupe de threads est un objet de la classe ThreadGroup. Chaque thread appartient à un groupe de threads unique est spécifié lors de la création du thread et ne peut pas être utilisé pendant tout le cycle de vie de. le fil. Vous pouvez spécifier le groupe de threads auquel le thread appartient en appelant le constructeur de classe Thread contenant un paramètre de type ThreadGroup. S'il n'est pas spécifié, le thread est par défaut le groupe de threads système nommé system.
En Java, tous les groupes de threads doivent être créés explicitement, à l'exception des groupes de threads système prédéfinis. En Java, chaque groupe de threads, à l'exception du groupe de threads système, appartient à un autre groupe de threads auquel il appartient lors de la création d'un groupe de threads. S'il n'est pas spécifié, il appartient au groupe de threads système par défaut. De cette façon, tous les groupes de threads forment une arborescence avec le groupe de threads système comme racine.
Java nous permet d'opérer sur tous les threads d'un groupe de threads en même temps. Par exemple, nous pouvons définir la priorité de tous les threads qu'il contient en appelant la méthode correspondante du groupe de threads, et nous pouvons également démarrer ou bloquer tous les threads du groupe. il.
Un autre rôle important du mécanisme de groupe de threads de Java est la sécurité des threads. Le mécanisme de groupe de threads nous permet de distinguer les threads avec des caractéristiques de sécurité différentes grâce au regroupement, de gérer différemment les threads de différents groupes et de prendre en charge l'adoption de mesures de sécurité inégales grâce à la structure hiérarchique des groupes de threads. La classe ThreadGroup de Java fournit un grand nombre de méthodes pour nous faciliter l'exploitation de chaque groupe de threads dans l'arborescence des groupes de threads et de chaque thread du groupe de threads.
État du thread À un moment donné, un thread ne peut être que dans un seul état.
NOUVEAU
Les threads qui n'ont pas encore été démarrés sont dans cet état.
EXÉCUTIF
Les threads exécutés dans la machine virtuelle Java sont dans cet état.
BLOQUÉ
Un thread bloqué et en attente d’un verrouillage du moniteur se trouve dans cet état.
EN ATTENDANT
Un thread qui attend indéfiniment qu’un autre thread effectue une opération spécifique est dans cet état.
L'état du thread d'un thread en attente. Un thread est dans un état d'attente car il a appelé l'une des méthodes suivantes :
Object.wait sans valeur de délai d'attente
Thread.join sans valeur de délai d'attente
LockSupport.park
Un thread en état d'attente attend qu'un autre thread effectue une opération spécifique. Par exemple, un thread qui a appelé Object.wait() sur un objet attend qu'un autre thread appelle Object.notify() ou Object.notifyAll() sur cet objet. Le thread qui a appelé Thread.join() attend la fin du thread spécifié.
TIMED_WAITING
Un thread qui attend qu'un autre thread effectue une opération qui dépend du temps d'attente spécifié se trouve dans cet état.
L'état du thread d'un thread en attente avec un temps d'attente spécifié. Un thread est dans un état d'attente temporisé en appelant l'une des méthodes suivantes avec un temps d'attente positif spécifié :
Sujet.sleep
Object.wait avec valeur de délai d'attente
Thread.join avec valeur de délai d'attente
LockSupport.parkNanos
LockSupport.parkUntil
TERMINÉ
Un thread quitté est dans cet état.