Modèle de mémoire
Le modèle de mémoire est un protocole de fonctionnement spécifique pour une mémoire spécifique ou Le cache résume le processus d’accès en lecture et en écriture. Son objectif principal est de définir des règles d'accès pour diverses variables du programme.
Mémoire principale et mémoire de travail
Toutes les variables sont stockées dans la mémoire principale. Chaque thread possède également sa propre mémoire de travail est une copie de la mémoire principale des variables utilisées par le thread. Les opérations telles que la lecture et l'affectation doivent être effectuées dans la mémoire de travail et les variables de la mémoire principale ne peuvent pas être lues directement.
Opérations d'interaction inter-mémoire
Copier de la mémoire principale vers la mémoire de travail : effectuez les opérations de lecture et de chargement de manière séquentielle.
La mémoire de travail est synchronisée avec la mémoire principale : opérations de stockage et d'écriture.
Caractéristiques du volatile
Volatile a la même fonction que synchronisé, mais est plus léger que synchronisé. Ses principales caractéristiques sont les deux points suivants :
Garantir la visibilité de cette variable sur tous les fils de discussion
Qu'est-ce que cela signifie ? Cela signifie que lorsqu'un thread modifie la valeur de cette variable, la nouvelle valeur est immédiatement connue des autres threads. Les variables ordinaires ne peuvent pas faire cela. La valeur des variables ordinaires doit être transférée entre les threads via la mémoire principale. Par exemple, le thread A modifie la valeur d'une variable ordinaire, puis la réécrit dans la mémoire principale. Une fois que A a terminé la réécriture puis lu dans la mémoire principale, la nouvelle valeur de la variable sera visible pour le thread B.
Désactiver l'optimisation de la réorganisation des instructions
Parce que la réorganisation des instructions interférera avec l'exécution simultanée du programme.
Multi-threading
Pourquoi avez-vous besoin du multithreading ?
L'écart entre la vitesse de calcul de l'ordinateur et la vitesse de son sous-système de stockage et de communication est trop grand, et beaucoup de temps est consacré aux E/S disque, à la communication réseau, et l'accès aux bases de données. L'utilisation du multithreading permet de mieux utiliser le processeur.
Quels sont les scénarios d'application simultanée ?
Profiter pleinement du processeur de votre ordinateur
Un serveur fournit des services à plusieurs clients en même temps
Comment rendre le processeur interne Les unités de calcul sont-elles pleinement utilisées ?
Ajouter une couche de cache
Copiez les données nécessaires à l'opération dans le cache afin que l'opération puisse Faites-le rapidement. Lorsque l'opération est terminée, elle est synchronisée avec la mémoire à partir du cache, de sorte que le processeur n'a pas à attendre des lectures et écritures lentes dans la mémoire. Il reste cependant un problème à considérer : comment garantir la cohérence du cache.
Optimiser l'exécution dans le désordre du code d'entrée
Implémentation du thread
Utiliser les threads du noyau pour implémenter
Un thread du noyau est un thread directement pris en charge par le noyau du système d'exploitation.
Utiliser les threads utilisateur pour implémenter
L'établissement, la synchronisation, la destruction et la planification des threads utilisateur sont entièrement réalisés en mode utilisateur sans l'aide du noyau, et le noyau aussi Une implémentation qui ignore l'existence des threads. Cette implémentation est rarement utilisée.
Utiliser les threads utilisateur plus légers pour une implémentation hybride
Fusionné ensemble
Planification des threads
Planification des threads fait référence au processus par lequel le système alloue les droits d'utilisation du processeur aux threads. Il en existe deux types principaux : collaboratif et préemptif.
Coopératif
Le temps d'exécution du thread est contrôlé par le thread lui-même. Lorsque le thread aura fini d'exécuter son travail, il informera activement le système de passer à un autre. fil de discussion.
L'avantage est qu'il est simple à mettre en œuvre et qu'il n'y a pas de problème de synchronisation des threads. L'inconvénient est que s'il y a un problème avec la programmation d'un thread et qu'il n'est pas demandé au système de changer de thread, le programme y sera toujours bloqué, ce qui peut facilement provoquer un crash du système.
Préemptif
Les threads se verront attribuer un temps d'exécution par le système, et le changement de thread n'est pas déterminé par lui-même. Il s'agit de la méthode de planification des threads utilisée par Java.
Sécurité des threads
Lorsque plusieurs threads accèdent à un objet, si la planification de ce thread dans l'environnement d'exécution n'est pas prise en compte et exécution alternative, il n'est pas nécessaire de synchroniser davantage ou de toute autre opération de coordination sur l'appelant. Le comportement d'appel de cet objet peut obtenir le résultat correct, alors cet objet est sûr.
Classification des données partagées
Immuable
Les données partagées immuables sont des données modifiées avec final, qui doivent être thread-safe. Si les données partagées sont une variable de type de base, utilisez simplement le mot-clé final lors de leur définition.
Si les données partagées sont un objet, alors le comportement de l'objet ne doit pas affecter son état. Vous pouvez déclarer toutes les variables avec un état dans l'objet comme finales. Par exemple, la classe String est une classe immuable
Absolument thread-safe
Marquez-vous comme une classe thread-safe dans l'API Java La plupart d'entre elles ne sont pas absolument thread-safe. Par exemple, Vector est une collection thread-safe et toutes ses méthodes sont modifiées pour être synchronisées, mais dans un environnement multithread, elle n'est toujours pas synchronisée.
Sécurité relative des threads
La sécurité relative des threads est ce que nous appelons habituellement la sécurité des threads. garantir que les opérations individuelles sur cet objet sont thread-safe. Cependant, pour certaines séquences spécifiques d'appels consécutifs, il peut être nécessaire d'utiliser des moyens de synchronisation supplémentaires côté appelant pour garantir la justesse des appels.
La plupart des classes thread-safe appartiennent à ce type.
Compatible avec les threads
L'objet lui-même n'est pas linéairement sûr, mais peut être créé en utilisant correctement la synchronisation sur le côté appelant Pour garantir que les objets peuvent être utilisés en toute sécurité dans des environnements concurrents. La plupart des classes qui ne sont pas thread-safe entrent dans cette catégorie.
Opposition de thread
Quoi qu'il en soit, il ne peut pas être utilisé simultanément dans un environnement multithread, tel que System.setIn(), System.SetOut(). L'un modifie l'entrée et l'autre modifie la sortie. Les deux ne peuvent pas être exécutés « alternativement ».
Méthode de mise en œuvre
Méthode 1 : Synchronisation mutuellement exclusive - stratégie de concurrence pessimiste
(1) synchronisé
Le principe est : après compilation, ce mot clé sera dans le bloc synchronisé Les deux bytecode les instructions Monitorenter et MonitorExit sont formées respectivement avant et après. Lorsque l'instruction Monitorenter est exécutée, le programme tentera d'acquérir le verrou de l'objet. S'il peut être acquis, le compteur de verrouillage sera +1. En conséquence, lorsque l'instruction Monitorexit sera exécutée, le compteur de verrouillage sera -1. Lorsque le compteur atteint 0, le verrou est libéré.
Ses caractéristiques sont : il est réentrant pour le même thread ; le bloc de synchronisation empêchera les autres threads suivants d'entrer avant que le thread entré ne termine son exécution.
Le scénario d'utilisation est le suivant : utilisez-le uniquement lorsque cela est absolument nécessaire, car il est lourd.
(2) ReentrantLock
Ce verrou réentrant est une classe sous le package java.util.concurrent (JUC). Ses fonctionnalités avancées incluent : les attentes peuvent être interrompues, des verrous équitables peuvent être mis en œuvre et les verrous peuvent être liés à plusieurs conditions.
Méthode 2 : Synchronisation non bloquante - stratégie de concurrence optimiste
Effectuez d'abord l'opération, s'il n'y a pas d'autres threads en compétition pour les données partagées, l'opération réussit ; s'il y a un conflit pour les données partagées et qu'un conflit se produit, d'autres mesures compensatoires seront prises.
Méthode 3 : Pas de solution de synchronisation
Si un La méthode n'implique pas de partage de données, aucune mesure de synchronisation n'est donc nécessaire. Tels que le code reproductible et le stockage local des threads.
(1) Code réentrant
Si le résultat de retour d'une méthode est prévisible et peut renvoyer le même résultat tant que les mêmes données sont saisies, alors il répond aux exigences de réentrance.
(2) Stockage local du thread
Si les données requises dans un morceau de code doivent être partagées avec d'autres codes et que les codes qui partagent des données sont exécutés dans le même thread, de cette manière , nous pouvons La plage visible des données partagées est limitée à un seul thread, de sorte qu'aucune synchronisation n'est nécessaire pour garantir qu'il n'y a pas de problèmes de conflit de données entre les threads.
Optimisation du verrouillage
Spin adaptatif
En raison du blocage ou du réveil d'un fil JAVA Le Le système d'exploitation doit changer l'état du processeur pour terminer, et cette transition d'état nécessite du temps processeur. Si le contenu du bloc de code de synchronisation est trop simple, il est probable que la transition d'état prendra plus de temps que le temps d'exécution du code utilisateur.
Afin de résoudre ce problème, nous pouvons demander au thread qui demande plus tard le verrou "d'attendre un moment", d'exécuter une boucle occupée et de tourner. Aucun temps d'exécution du processeur n'est abandonné pour le moment. Si la rotation dépasse le nombre limité de fois et que le verrou n'est toujours pas obtenu avec succès, la méthode traditionnelle sera utilisée pour suspendre le thread.
Alors, qu'est-ce que le spin adaptatif ?
C'est sur le même objet de verrouillage. Si l'attente de rotation vient d'obtenir le verrou avec succès, la machine virtuelle pensera que la probabilité d'obtenir le verrou par rotation cette fois est assez élevée, et le fera. laissez-le tourner. L’attente dure relativement plus longtemps. À l’inverse, si le spin réussit rarement à acquérir un surjet, le processus de spin peut être omis.
Élimination du verrouillage
fait référence à la machine virtuelle juste-in- time compilateur au moment de l'exécution, élimine les verrous qui nécessitent une synchronisation dans certains codes mais qui sont détectés comme étant peu susceptibles d'avoir une concurrence de données partagées.
Dégrossissage de serrure
Si une série d'opérations continues verrouillent et déverrouillent à plusieurs reprises le même objet, ou même si l'opération de verrouillage se produit dans le corps de la boucle, alors même s'il n'y a pas de concurrence de threads, des opérations fréquentes de synchronisation d'exclusion mutuelle entraîneront une perte de performances inutile. .
Si la machine virtuelle détecte qu'une série d'opérations fragmentées verrouillent toutes le même objet, elle élargira la portée de la synchronisation du verrouillage à l'extérieur de l'ensemble de la séquence d'opérations, de sorte qu'elle n'aura besoin de se verrouiller qu'une seule fois.
Verrouillage léger
Sans concurrence multithread, réduisez les verrous lourds traditionnels au détriment des performances pénalités à l’aide des mutex du système d’exploitation.
Scénarios applicables : pas de concurrence réelle, plusieurs threads utilisent des verrous en alternance ; la concurrence de verrous à court terme est autorisée.
Verrouillage des biais
Le verrouillage des biais est utilisé pour réduire l'utilisation de l'absence de contention et un seul thread Dans le cas des verrous, consommation de performances provoquée par l'utilisation de verrous légers. Un verrou léger nécessite au moins un CAS chaque fois qu'il demande et libère un verrou, mais un verrou biaisé ne nécessite qu'un seul CAS lors de l'initialisation.
Scénario applicable : il n'y a pas de véritable concurrence et seul le premier fil de discussion à demander le verrou utilisera le verrou à l'avenir.
Ce qui précède est une introduction détaillée à la concurrence efficace dans les machines virtuelles JAVA. 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!