Je me souviens que lorsque j'ai commencé à apprendre Java, synchronisé a été rencontré lorsque j'ai rencontré une situation multi-lancement. Par rapport à nous à ce moment-là, la synchronisée était si magique et puissante. À cette époque, nous lui avons donné un nom de «synchronisation», qui est également devenu un bon médicament pour nous pour résoudre des situations multiples. Mais à mesure que nous apprenons, nous savons que Synchronisé est une serrure poids lourd, et par rapport à la serrure, il semblera si volumineux que nous pensons qu'il n'est pas si efficace et l'abandonnera lentement.
Certes, avec les différentes optimisations faites par Javas SE 1.6 en synchronisé, synchronisée n'apparaîtra pas aussi lourd. Ci-dessous, suivez LZ pour explorer le mécanisme de mise en œuvre de la synchronisation, comment Java l'optimise, le mécanisme d'optimisation de verrouillage, la structure de stockage de verrouillage et le processus de mise à niveau;
Principe de mise en œuvre
Synchronisé peut s'assurer que la méthode ou le bloc de code est en cours d'exécution, et une seule méthode peut saisir la zone critique en même temps. Dans le même temps, il peut également garantir la visibilité de la mémoire des variables partagées.
Chaque objet de Java peut être utilisé comme verrouillage, qui est la base de la synchronisation pour implémenter la synchronisation:
Méthode de synchronisation commune, Lock est la méthode de synchronisation statique de l'objet d'instance actuel, Lock est le bloc de méthode de synchronisation de l'objet de classe actuel, Lock est l'objet entre parenthèses Lorsqu'un thread accède au bloc de code de synchronisation, il doit d'abord obtenir le verrou pour exécuter le code de synchronisation. Lorsqu'une exception est sortie ou lancée, la serrure doit être libérée. Alors, comment met-il en œuvre ce mécanisme? Regardons d'abord un code simple:
classe publique SynchronizedTest {public synchronisé void test1 () {} public void test2 () {synchronisé (this) {}}}Utilisez l'outil Javap pour afficher les informations de fichier de classe générées pour analyser la mise en œuvre de la synchronisation
Comme on peut le voir à partir de ce qui précède, le bloc de code de synchronisation est implémenté à l'aide d'instructions de surveillant et de monitorexit. La méthode de synchronisation (il ne montre pas que vous devez consulter l'implémentation de JVM sous-jacente) repose sur l'implémentation ACC_SYNCHRONISE sur le modificateur de la méthode.
Bloc de code synchrone: L'instruction du surveillant est insérée dans la position de début du bloc de code de synchronisation, et l'instruction monitorexit est insérée dans la position finale du bloc de code de synchronisation. Le JVM doit s'assurer que chaque surveillant a une monitorexit correspondant à lui. Tout objet a un moniteur qui lui est associé, et lorsqu'un moniteur est maintenu, il sera verrouillé. Lorsqu'un thread exécute l'instruction de surveillant, il essaiera d'obtenir la propriété du moniteur correspondant à l'objet, c'est-à-dire, essayez d'obtenir le verrou de l'objet;
Méthode synchronisée: La méthode synchronisée sera traduite en appels de méthode ordinaire et instructions de retour telles que: Instructions invokevirtual et areturn. Il n'y a aucune instruction spéciale au niveau des bytecodes VM pour implémenter la méthode modifiée synchronisée. Au lieu de cela, la position de l'indicateur synchronisé 1 dans le champ Méthode Access_Flags de la méthode est placée dans le tableau de la méthode du fichier de classe, indiquant que la méthode est une méthode synchronisée et utilise l'objet qui appelle la méthode ou la classe appartenant à la méthode pour représenter Klass en tant qu'objet de verrouillage (voir: http://www.vevb.com/article/129245.htm)
Continuons à l'analyser, mais avant d'aller plus loin, nous devons comprendre deux concepts importants: l'en-tête et moniteur de l'objet Java.
En-tête d'objet Java, moniteur
Les en-têtes et moniteurs d'objets Java sont la base de la mise en œuvre de la synchronisation! Ce qui suit est une introduction détaillée à ces deux concepts.
En-tête d'objet Java
Le verrou utilisé pour synchronisé est situé dans l'en-tête de l'objet Java, alors quel est l'en-tête de l'objet Java? L'en-tête d'objet de la machine virtuelle Hotspot comprend principalement deux parties de données: Mark Word (Mark Field) et Klass Pointer (type pointeur). Klass Point est un pointeur vers les métadonnées de la classe de l'objet. La machine virtuelle utilise ce pointeur pour déterminer quelle instance de classe l'objet est. Mark Word est utilisé pour stocker les propres données d'exécution de l'objet. C'est la clé de la mise en œuvre de verrous légers et de verrous biaisés, donc ce qui suit se concentrera sur celui-ci.
Marquez le mot.
Mark Word est utilisé pour stocker les données d'exécution de l'objet lui-même, tel que le code de hash (HashCode), l'âge de génération GC, le drapeau d'état de verrouillage, les verrous tenus par des threads, l'identifiant de thread biaisé, une horodatage biaisée, etc. La machine virtuelle JVM peut déterminer la taille de l'objet Java à travers les informations de métadonnées de l'objet Java, mais ne peut pas confirmer la taille du tableau à partir des métadonnées du tableau, donc une pièce est utilisée pour enregistrer la longueur du tableau. La figure suivante est la structure de stockage de l'en-tête d'objet Java (machine virtuelle 32 bits):
Les informations d'en-tête d'objet sont un coût de stockage supplémentaire indépendant des données définies par l'objet lui-même. Cependant, compte tenu de l'efficacité spatiale de la machine virtuelle, Mark Word est conçu comme une structure de données non fixée pour stocker autant de données que possible dans une très petite mémoire d'espace. Il réutilisera son propre espace de stockage en fonction de l'état de l'objet. C'est-à-dire que Mark Word changera avec le fonctionnement du programme, et l'état de changement est le suivant (machine virtuelle 32 bits):
Une brève introduction aux en-têtes d'objets Java, jetons un coup d'œil à moniteur.
Moniteur
Qu'est-ce que le moniteur? Nous pouvons le comprendre comme un outil de synchronisation ou un mécanisme de synchronisation, qui est généralement décrit comme un objet.
Comme tout est un objet, tous les objets Java sont des moniteurs naturels, et chaque objet Java a le potentiel de devenir un moniteur, car dans la conception Java, chaque objet Java porte une serrure invisible car elle sort de l'utérus. Il s'appelle un verrouillage interne ou un verrouillage de moniteur.
Le moniteur est une structure de données détenue par des threads. Chaque thread a une liste des enregistrements de moniteur disponibles et une liste mondiale disponible. Chaque objet verrouillé sera associé à un moniteur (verrouillage dans le markword de l'en-tête d'objet pointe vers l'adresse de début du moniteur). Dans le même temps, il existe un champ de propriétaire dans le moniteur qui stocke l'identifiant unique du fil qui possède le verrou, indiquant que le verrou est occupé par le fil. Sa structure est la suivante:
Propriétaire: Null au début signifie qu'aucun fil ne possède actuellement le monitorrecord. Lorsque le thread possède avec succès le verrou, il enregistre l'identité unique du fil et lorsque le verrou est libéré, il est défini sur NULL;
EntryQ: Associez un système Mutex (Semaphore) pour bloquer tous les threads qui tentent de verrouiller monitorRecord.
RCTHIS: représente le nombre de tous les threads bloqués ou en attente sur le monitorrecord.
Nest: utilisé pour implémenter le comptage des verrous de rentrée.
HashCode: enregistre la valeur de code hashcode copiée à partir de l'en-tête de l'objet (peut également contenir GCAGE).
Candidat: utilisé pour éviter le blocage inutile ou en attendant que les threads se réveillent, car un seul thread peut posséder avec succès le verrou à chaque fois. Si le fil précédent qui libère la serrure réveille tous les fils de blocage ou d'attente à chaque fois, il provoquera une commutation de contexte inutile (du blocage au prêt, puis à un blocage à nouveau en raison de la défaillance du verrouillage de la compétition) et conduira ainsi à une dégradation sévère des performances. Le candidat n'a que deux valeurs possibles: 0 signifie qu'il n'y a pas de thread qui doit être réveillé jusqu'à 1 signifie réveiller un thread successeur pour rivaliser pour le verrou.
Référence: Parlez de synchronisé dans la concurrence Java
Nous savons que Synchronized est une serrure poids lourd et son efficacité n'est pas très bonne. En même temps, ce concept a toujours été dans notre esprit. Cependant, l'implémentation de la synchronisation a été optimisée dans JDK1.6, ce qui ne le rend pas si lourd. Alors, quelles méthodes d'optimisation le JVM utilise-t-il?
Optimisation de verrouillage
JDK1.6 introduit de nombreuses optimisations à la mise en œuvre des serrures, telles que les verrous de spin, les verrous de spin adaptatifs, l'élimination des verrous, la grossièreté des verrous, les verrous de biais, les verrous légers et d'autres technologies pour réduire le fonctionnement des frais généraux.
Il y a quatre principaux états de verrouillage, à savoir: un état sans serrure, l'état de verrouillage biaisé, l'état de verrouillage léger et l'état de verrouillage des poids lourds. Ils se passeront progressivement avec la compétition féroce. Notez que les verrous peuvent être mis à niveau et ne peuvent pas être rétrogradés. Cette stratégie consiste à améliorer l'efficacité de l'obtention et de la libération de serrures.
Verrouillage de rotation
Le blocage du thread et le réveil nécessitent que le CPU passe de l'état de l'utilisateur à l'état central. Le blocage et le réveil fréquents sont une tâche lourde pour le CPU, et cela mettra inévitablement beaucoup de pression sur les performances simultanées du système. Dans le même temps, nous avons constaté que dans de nombreuses applications, l'état de verrouillage du verrouillage de l'objet ne durera que pendant une très courte période de temps. Il est très indigne de bloquer fréquemment et de réveiller le fil pendant cette très courte période. Ainsi, un verrouillage de spin est introduit.
Qu'est-ce que Spin Lock?
Le soi-disant verrouillage de spin est de permettre au fil d'attendre une période de temps et de ne pas être suspendu immédiatement pour voir si le fil tenant le verrou relâchera rapidement le verrou. Comment attendre? Effectuez simplement un cycle sans signification (spin).
L'attente de spin ne peut pas remplacer le blocage, parlons des exigences pour le nombre de processeurs (multi-fond, il semble qu'il n'y ait plus de processeur à noyau maintenant). Bien qu'il puisse éviter les frais généraux causés par la commutation du thread, il prend le temps du processeur. Si le fil tenant le verrou libère rapidement le verrou, l'efficacité du spin sera très bonne. Au contraire, le fil de spin consommera les ressources de traitement en vain. Il ne fera aucun travail significatif. Il occupe généralement la fosse et ne merde pas, ce qui entraînera plutôt des déchets de performances. Par conséquent, le temps pour l'attente de spin (nombre de tours) doit avoir une limite. Si le spin dépasse le temps défini et n'a toujours pas obtenu la serrure, elle doit être suspendue.
Spinlock est introduit dans JDK1.4.2 et est désactivé par défaut, mais il peut être activé avec -xx: + usespinning, et est activé par défaut dans JDK1.6. Le nombre par défaut de spins en même temps est 10 fois, qui peut être ajusté par le paramètre -xx: preblockspin;
Si vous ajustez le nombre de spins de spins du verrouillage de spin à travers le paramètre -xx: préblockspin, cela provoquera de nombreux inconvénients. Si j'ajuste le paramètre à 10, mais de nombreux threads du système libèrent le verrou lorsque vous sortez (si vous tournez une ou deux fois, vous pouvez obtenir le verrou), seriez-vous gêné? Par conséquent, JDK1.6 a introduit des verrous de spin adaptatifs, ce qui rend les machines virtuelles plus intelligentes et plus intelligentes.
Adapter le verrouillage de spin
JDK1.6 présente un verrou de rotation plus intelligent, à savoir le verrou de rotation adaptatif. La soi-disant adaptation signifie que le nombre de tours n'est plus fixe, il est déterminé par le temps de spin sur le même serrure et l'état du propriétaire de l'écluse. Comment faire? Si le fil tourne avec succès, le nombre de tours sera plus la prochaine fois, car la machine virtuelle estime que depuis la dernière fois qu'il a réussi, le spin sera probablement à nouveau réussi et permettra au rotation d'attendre plus de fois. Au contraire, si peu de tours peuvent réussir pour une certaine serrure, le nombre de tours sera réduit ou même omis lorsque la serrure est requise à l'avenir, afin de ne pas gaspiller les ressources de transformatrice.
Avec les verrous de spin adaptatifs, car les informations sur le fonctionnement du programme et la surveillance des performances continuent de s'améliorer, la prévision de la machine virtuelle de l'état de verrouillage du programme deviendra de plus en plus précise et les machines virtuelles deviendront plus intelligentes.
Élimination des verrous
Afin d'assurer l'intégrité des données, nous devons synchroniser cette partie de l'opération lors de l'exécution des opérations, mais dans certains cas, le JVM détecte qu'il n'y a aucune possibilité de concurrence de données partagée, c'est pourquoi le JVM verrouille ces serrures de synchronisation. La base de l'élimination des verrous est le support des données pour l'analyse d'évasion.
S'il n'y a pas de concurrence, pourquoi avez-vous encore besoin d'ajouter une serrure? L'élimination des verrous peut donc gagner du temps à demander sans signification les verrous. Si la variable s'échappe est nécessaire pour que les machines virtuelles déterminent si l'analyse du flux de données est utilisée, mais est-elle encore claire pour les programmeurs américains? Allons-nous ajouter une synchronisation avant le bloc de code qui sait clairement qu'il n'y a pas de concours de données? Mais parfois, le programme n'est pas ce que nous pensons? Bien que nous n'affichons pas le verrou, lorsque nous utilisons certaines API intégrées JDK, telles que StringBuffer, Vector, Hashtable, etc., il y aura une opération de verrouillage invisible à l'heure actuelle. Par exemple, la méthode APPEND () de StringBuffer et la méthode Add () de Vector:
public void vectOrest () {vector <string> vector = new vector <string> (); pour (int i = 0; i <10; i ++) {vector.add (i + ""); } System.out.println (vecteur); }Lors de l'exécution de ce code, le JVM peut clairement détecter que le vecteur variable n'échappe pas à la méthode VectOrest (), afin que le JVM puisse éliminer hardiment l'opération de verrouillage à l'intérieur du vecteur.
Verrouillage
Nous savons que lorsque vous utilisez un verrou de synchronisation, la portée du bloc de synchronisation doit être aussi petite que possible - se synchronise uniquement dans la portée réelle des données partagées. Le but de cela est de minimiser le nombre d'opérations qui doivent être synchronisés autant que possible. S'il y a une compétition de verrouillage, le fil en attente de la serrure peut également obtenir le verrou dès que possible.
Dans la plupart des cas, la vue ci-dessus est correcte et LZ a toujours adhéré à cette vue. Cependant, si une série d'opérations de verrouillage et de déverrouillage continues peut entraîner des pertes de performances inutiles, de sorte que le concept de verrouillage Slutty est introduit.
Le concept de calomnie de verrouillage est plus facile à comprendre, qui consiste à connecter plusieurs opérations de verrouillage et de déverrouillage continues ensemble et de se développer dans une serrure avec une gamme plus grande. Comme indiqué dans l'exemple ci-dessus: le vecteur doit ajouter une opération de verrouillage à chaque fois qu'il ajoute. Le JVM détecte que le même objet (vecteur) est en continu verrouillé et déverrouillé, et fusionnera une plus grande gamme d'opérations verrouillées et déverrouillées, c'est-à-dire que l'opération verrouillée et déverrouillée sera déplacée à l'extérieur de la boucle pour la boucle.
Verrouillage léger
L'objectif principal de l'introduction de verrous légers est de réduire la consommation de performances causée par l'utilisation des mutex du système d'exploitation sous la prémisse de plusieurs compétitions sans fil multiples. Lorsque la fonction de verrouillage de polarisation est désactivée ou que plusieurs threads rivalisent pour les verrous de biais, le verrou de polarisation sera mis à niveau vers un verrou léger, alors le verrou léger sera tenté et les étapes sont les suivantes:
Se verrouiller
Déterminez si l'objet actuel est dans un état sans serrure (HashCode, 0, 01). Si c'est le cas, le JVM créera d'abord un espace appelé Lock Record dans la trame de pile du thread actuel pour stocker la copie de mot de marque actuelle de l'objet Lock (le fonctionnaire ajoute un préfixe déplacé à cette copie, à savoir le mot de marque déplacé); Sinon, exécutez l'étape (3);
Le JVM utilise l'opération CAS pour essayer de mettre à jour le mot de marque de l'objet à un enregistrement pointant de correction pour verrouiller. S'il indique avec succès que le verrou est contesté, l'indicateur de verrouillage sera modifié en 00 (indiquant que l'objet est dans un état de verrouillage léger) et effectue une opération de synchronisation; S'il échoue, effectuez l'étape (3);
Déterminez si le mot de marque de l'objet actuel pointe vers le cadre de pile du thread actuel. Si c'est le cas, cela signifie que le thread actuel maintient déjà le verrouillage de l'objet actuel, puis exécute directement le bloc de code synchrone; Sinon, cela peut seulement signifier que l'objet de verrouillage a été saisi par d'autres threads. À l'heure actuelle, le verrouillage léger doit être étendu dans un verrou de poids lourd, le bit du drapeau de verrouillage devient 10, et le fil en attente plus tard entrera à l'état de blocage;
Relâchez le verrouillage
La libération de serrures légères est également effectuée par les opérations CAS, et les étapes principales sont les suivantes:
Supprimer les données enregistrées dans le mot de marque déplacé dans le verrouillage léger de l'acquisition;
Remplacez les données récupérées dans Mark Word par l'opération CAS. S'il réussit, cela signifie que le verrou est libéré avec succès, sinon il sera exécuté (3);
Si le remplacement de l'opération CAS échoue, cela signifie que d'autres threads essaient d'acquérir le verrou et le fil suspendu doit être éveillé lors de la libération du verrou.
Pour les serrures légères, la base de l'amélioration des performances est que "pour la plupart des serrures, il n'y aura pas de concurrence tout au long du cycle de vie". Si cette base est rompue, en plus des frais généraux mutuellement exclusifs, il existe des opérations CAS supplémentaires. Par conséquent, dans le cas de la compétition multi-thread, les serrures légères sont plus lentes que les serrures poids lourds;
La figure suivante montre le processus d'acquisition et de libération des verrous légers
Verrouillage positif
L'objectif principal de l'introduction de verrous biaisés est de minimiser les chemins d'exécution de verrouillage légers inutiles sans concurrence multi-thread. Ce qui précède a mentionné que le fonctionnement de verrouillage et de déverrouillage des verrous légers nécessite de s'appuyer sur plusieurs instructions atomiques CAS. Alors, comment le verrouillage du biais réduit-il les opérations CAS inutiles? Nous pouvons voir la structure du travail de marque. Il vous suffit de vérifier s'il s'agit d'un verrou biaisé, le verrou est identifié comme et le threadid. Le flux de traitement est le suivant:
Se verrouiller
Détectez si Mark Word est dans un état biaisable, c'est-à-dire s'il s'agit du verrouillage biaisé 1, et le bit d'identification de verrouillage est 01;
S'il s'agit d'un état biaisable, testez si l'ID de thread est l'ID de thread actuel. Si c'est le cas, exécutez l'étape (5), sinon exécutez l'étape (3);
Si l'ID de filetage n'est pas l'ID de filetage actuel, le verrou est concouru par l'opération CAS. Si le concours réussit, l'ID de thread de Mark Word est remplacé par l'ID de thread actuel, sinon le thread (4);
Le verrouillage de la compétition CAS échoue, ce qui prouve qu'il existe actuellement une situation de concurrence multi-thread. Lorsque le point de sécurité global est atteint, le fil qui obtient le verrou basé est suspendu, le verrouillage biaisé est mis à niveau vers un verrou léger, puis le thread bloqué au point de sécurité continue d'exécuter le bloc de code synchrone;
Exécuter des blocs de code synchrone
Libérez les verrous. La libération de la serrure biaisée adopte un mécanisme dans lequel seule la concurrence peut libérer le verrou. Le thread ne libérera pas activement le verrou biaisé et doit attendre que d'autres threads rivalisent. L'annulation des verrous biaisés nécessite d'attendre un point de sécurité mondial (ce point est qu'il n'y a pas de code exécuté). Les étapes sont les suivantes:
Pause le fil avec un verrou basé et déterminez si la pierre de verrouillage est toujours à l'état verrouillé;
Déverrouiller le biais vers SU et Restaurer à l'état sans verrouillage (01) ou à l'état de verrouillage léger;
La figure suivante montre le processus d'acquisition et de libération des verrous biaisés
Verrouillage des poids lourds
Le verrouillage du poids est également appelé moniteur d'objet (moniteur) dans JVM. Il est très similaire à Mutex dans C. En plus d'avoir Mutex (0 | 1) une fonction mutuellement exclusive, il est également responsable de la mise en œuvre de la fonction du sémaphore, c'est-à-dire qu'il contient au moins une file d'attente de verrous de compétition et une file d'attente de blocage du signal (file d'attente d'attente). Le premier est responsable de l'exclusion mutuelle et le second est utilisé pour la synchronisation des threads.
Les verrous des poids lourds sont mis en œuvre par le biais de moniteurs (moniteurs) à l'intérieur d'objets, où l'essence des moniteurs est de s'appuyer sur la mise en œuvre de la serrure Mutex du système d'exploitation sous-jacent. La commutation entre les threads du système d'exploitation nécessite le passage de l'état utilisateur à l'état du noyau, et le coût de commutation est très élevé.
Résumer
Ce qui précède est tout le contenu de cet article sur l'explication détaillée du principe de mise en œuvre de synchronisé en Java. J'espère que ce sera utile à tout le monde. S'il y a des lacunes, veuillez laisser un message pour le signaler. Merci vos amis pour votre soutien pour ce site!