1. Verrouillage des poids lourds
Dans l'article précédent, nous avons introduit l'utilisation des principes synchronisés et de ses principes de mise en œuvre. Nous devons maintenant savoir que Synchronized est implémenté via un verrouillage de moniteur à l'intérieur de l'objet. Cependant, le verrouillage du moniteur est essentiellement mis en œuvre en s'appuyant sur le verrou Mutex du système d'exploitation sous-jacent. Le système d'exploitation doit basculer entre les threads de l'état utilisateur à l'état central. Ceci est très coûteux et la conversion entre les États prend relativement longtemps. C'est pourquoi la synchronisation est inefficace. Par conséquent, nous appelons cette serrure qui repose sur la mise en œuvre du verrou de mutex du système d'exploitation "un verrou de poids lourd". Le noyau des différentes optimisations faites à synchronisé dans JDK est de réduire l'utilisation de ce verrouillage des poids lourds. Après JDK1.6, afin de réduire la consommation de performances causée par l'obtention et la libération de verrous et l'amélioration des performances, des "serrures légères" et des "verrous biaisés" ont été introduits.
2. Verrouillage léger
Il existe quatre types d'états de verrouillage: l'état sans verrouillage, le verrouillage biaisé, le verrouillage léger et le verrouillage lourd. Avec la concurrence des verrous, les serrures peuvent être améliorées des verrous biaisés aux serrures légères, puis les serrures lourdes améliorées (mais la mise à niveau des verrous est à sens unique, ce qui signifie qu'ils ne peuvent passer que de bas à haut, et il n'y aura pas de dégradation des verrous). Dans JDK 1.6, le verrouillage de biais et le verrouillage léger sont activés par défaut. Nous pouvons également désactiver le verrouillage du biais par -xx: -UsebiasEdLocking. L'état de verrouillage est enregistré dans le fichier d'en-tête de l'objet, prenant le JDK 32 bits comme exemple:
Statut de verrouillage | 25 bits | 4 bits | 1 bits | 2 bits | ||
23 bits | 2 bits | Est-ce un verrouillage biaisé? | Bit du drapeau de verrouillage | |||
Verrouillage léger | Pointeur pour verrouiller les enregistrements dans la pile | 00 | ||||
Verrouillage des poids lourds | Pointeur vers mutex (verrouillage des poids lourds) | 10 | ||||
Tags GC | nul | 11 | ||||
Verrouillage positif | ID de fil | Époque | Les sujets sont majeurs | 1 | 01 | |
Sans verrouillage | HashCode de l'objet | Les sujets sont majeurs | 0 | 01 | ||
"Lightweight" est relatif aux serrures traditionnelles qui utilisent des mutex du système d'exploitation. Cependant, il est important de souligner d'abord que les verrous légers ne sont pas utilisés pour remplacer les serrures poids lourds. Son intention initiale est de réduire la consommation de performances générée par l'utilisation de serrures traditionnelles des poids lourds sans concurrence multi-thread. Avant d'expliquer le processus d'exécution des verrous légers, nous comprenons d'abord que les scénarios adaptés aux verrous légers sont le cas où les threads exécutent alternativement les blocs synchrones. Si le même verrou est accessible en même temps, le verrou léger se développera dans une serrure poids lourd.
1. Le processus de verrouillage du verrouillage léger
(1) Lorsque le code pénètre dans le bloc de synchronisation, si l'état de verrouillage de l'objet de synchronisation est sans serrure (l'indicateur de verrouillage est un état "01", qu'il soit verrouillé biaisé "0"), la machine virtuelle créera d'abord un espace appelé enregistrement de verrouillage dans la trame de pile du mot de marque actuel pour stocker la copie actuelle du mot de marque actuel de l'objet de verrouillage, qui est officiellement appelé mot de marque imprégné. À l'heure actuelle, l'état de la pile de threads et de l'en-tête d'objet est illustré à la figure 2.1.
(2) Copiez le mot de marque dans l'en-tête de l'objet et copiez-le dans l'enregistrement de verrouillage.
(3) Une fois la copie réussie, la machine virtuelle utilisera les opérations CAS pour essayer de mettre à jour le mot de marque de l'objet en un enregistrement de pointeur pour verrouiller, et pointer le pointeur du propriétaire dans l'enregistrement de verrouillage sur le mot de marque de l'objet. Si la mise à jour réussit, exécutez l'étape (3), sinon exécutez l'étape (4).
(4) Si cette action de mise à jour est réussie, le thread a le verrou de l'objet et que l'indicateur de verrouillage du mot de marque d'objet est défini sur "00", ce qui signifie que l'objet est dans un état verrouillé léger. À l'heure actuelle, l'état de la pile de threads et la tête d'objet est illustré à la figure 2.2.
(5) Si cette opération de mise à jour échoue, la machine virtuelle vérifiera d'abord si le mot de marque de l'objet pointe vers la trame de pile du thread actuel. Si c'est le cas, cela signifie que le thread actuel a déjà le verrouillage de l'objet, puis il peut saisir directement le bloc de synchronisation pour continuer l'exécution. Sinon, plusieurs threads rivalisent pour les verrous, et le verrou léger se développera dans une serrure poids lourd, et la valeur de l'état du drapeau de verrouillage deviendra "10". Le pointeur vers le verrouillage des poids lourds (mutex) est stocké dans le mot de marque, et le fil en attente de verrouillage entrera également dans l'état de blocage. Le thread actuel essaie d'utiliser Spin pour acquérir le verrou. Le spin consiste à éviter de bloquer le fil et à utiliser une boucle pour acquérir le verrou.
Figure 2.1 l'état de la pile et de l'objet avant l'opération CAS de verrouillage léger
Figure 2.2 L'état de la pile et de l'objet après le fonctionnement de CAS de verrouillage léger
2. Déverrouillage du processus de verrouillage léger:
(1) Essayez de remplacer l'objet mot de marque déplacé copié dans l'opération CAS à travers CAS.
(2) Si le remplacement est réussi, l'ensemble du processus de synchronisation sera terminé.
(3) Si le remplacement échoue, cela signifie que d'autres threads ont essayé d'acquérir le verrou (le verrou a été élargi à ce moment), le fil suspendu doit être éveillé lors de la libération du verrou.
3. Verrouillage positif
The introduction of bias lock is to minimize unnecessary lightweight lock execution paths without multi-thread competition, because the acquisition and release of lightweight locks depend on multiple CAS atomic instructions, while bias locks only need to rely on one CAS atomic instructions when replacing ThreadID (because the bias lock must be cancelled once a multi-thread competition occurs, so the performance loss of the cancel operation of bias lock must be less than the saved performance consumption of Cas Instructions atomiques). Comme mentionné ci-dessus, les verrous légers sont utilisés pour améliorer les performances lorsque les threads exécutent alternativement les blocs synchrones, tandis que les verrous biaisés sont utilisés pour améliorer davantage les performances lorsqu'un seul thread exécute des blocs synchrones.
1. Le processus d'acquisition de verrouillage biaisé:
(1) Accédez à si le drapeau du verrouillage de biais dans le mot de marque est défini sur 1 et si l'indicateur de verrouillage est 01 - confirme qu'il s'agit d'un état biaisable.
(2) S'il s'agit d'un état biaisable, testez si l'ID de thread pointe vers le thread actuel. Si c'est le cas, entrez l'étape (5), sinon entrez l'étape (3).
(3) Si l'ID de thread ne pointe pas vers le thread actuel, le verrou sera concouru via l'opération CAS. Si le concours réussit, définissez l'ID de thread dans Mark Word sur l'ID de thread actuel et exécutez (5); Si la concurrence échoue, exécutez (4).
(4) Si CAS ne parvient pas à acquérir un verrouillage de biais, cela signifie qu'il y a une concurrence. Lorsque le Global SafePoint est atteint, le fil qui obtient le verrouillage de biais est suspendu. Le verrouillage de polarisation est mis à niveau vers un verrou léger, et le fil bloqué sur SafePoint continue d'exécuter le code de synchronisation.
(5) Exécuter le code de synchronisation.
2. Libération de verrouillage biaisé:
La révocation du verrouillage biaisé est mentionnée dans la quatrième étape ci-dessus. Le verrouillage biaisé ne libèrera la serrure que lorsque d'autres threads essaieront de rivaliser pour le verrou biaisé, et le fil ne libère pas activement le verrou biaisé. L'annulation du verrou biaisé nécessite d'attendre le point de sécurité global (aucun bytecode n'est exécuté à ce stade). Il en suscitera d'abord le fil avec le verrouillage biaisé, déterminera si l'objet de verrouillage est dans un état verrouillé, puis revient au déverrouillé (le bit de drapeau est "01") ou un verrou léger (le bit de drapeau est "00") après avoir annulé le verrouillage biaisé.
3. Conversion entre le verrouillage des poids lourds, le verrouillage léger et le verrouillage du biais
Figure 2.3 Le diagramme de conversion des trois
Cette image est principalement un résumé du contenu ci-dessus. Si vous avez une bonne compréhension du contenu ci-dessus, l'image doit être facile à comprendre.
4. Autres optimisations
1. Spinning adaptatif: Depuis le processus d'obtention de verrous légers, nous savons que lorsqu'un fil ne fait pas d'opération de CAS lors de l'acquisition de serrures légères, il est nécessaire d'obtenir la verrouillage des poids lourds à travers le spin. Le problème est que le spin nécessite une consommation de processeur. Si le verrouillage ne peut pas être obtenu, le fil sera à l'état de spin et gaspillera les ressources CPU en vain. La façon la plus simple de résoudre ce problème est de spécifier le nombre de tours, par exemple, de le laisser faire du vélo 10 fois et d'entrer dans un état de blocage si la serrure n'est pas obtenue. Mais JDK adopte une approche plus intelligente - une rotation adaptative. Autrement dit, si le fil réussit, le nombre de tours sera plus la prochaine fois, et si le spin échoue, le nombre de spins sera réduit.
2. Par exemple:
package com.paddx.test.string; classe publique StringBufferSTest {stringBuffer stringBuffer = new StringBuffer (); public void append () {stringBuffer.append ("a"); StringBuffer.APPEND ("B"); StringBuffer.Apend ("C"); }}Ici, chaque fois que vous appelez la méthode StringBuffer.Apend, le verrouillage et le déverrouillage sont requis. Si la machine virtuelle détecte une série d'opérations de verrouillage et de déverrouillage sur le même objet, elle la fusionnera dans une plus large gamme d'opérations de verrouillage et de déverrouillage, c'est-à-dire que le verrouillage est effectué sur la méthode de première annexe et que le déverrouillage est effectué une fois la méthode de la dernière annexe terminée.
3. Élimination de verrouillage: l'élimination des verrous signifie le retrait des opérations de verrouillage inutiles. Selon Code Escape Technology, s'il est déterminé qu'un morceau de code et les données sur le tas ne s'échappera pas du thread actuel, il peut être considéré que ce morceau de code est en sécurité et il n'est pas nécessaire de le verrouiller. Regardez le programme suivant:
package com.paddx.test.concurrent; classe publique SynchronizedTest02 {public static void main (String [] args) {synchronizedtest02 test02 = new synchroniséTest02 (); // commence l'échauffement pour (int i = 0; i <10000; i ++) {i ++; } long start = System.currentTimemillis (); pour (int i = 0; i <100000000; i ++) {test02.Apnd ("ABC", "DEF"); } System.out.println ("Time =" + (System.CurrentTimeMillis () - START)); } public void append (string str1, string str2) {StringBuffer sb = new StringBuffer (); SB.APPEND (STR1) .APPEND (STR2); }}Bien que l'append de StringBuffer soit une méthode synchrone, le StringBuffer de ce programme appartient à une variable locale et n'échappera pas à la méthode. Par conséquent, ce processus est en fait en forme de file et peut éliminer le verrou. Voici le résultat de mon exécution locale:
Pour minimiser l'impact des autres facteurs, le verrouillage du biais (-xx: -UsebiasEdLocking) est désactivé ici. Grâce au programme ci-dessus, on peut voir que les performances ont été considérablement améliorées après l'élimination du verrouillage.
Remarque: Les résultats d'exécution peuvent être différents entre les différentes versions de JDK. La version JDK que j'utilise ici est 1.6.
5. Résumé
Cet article se concentre sur l'optimisation des synchronisés dans JDK, tels que des serrures légères et des serrures biaisées, mais ces deux serrures ne sont pas complètement sans lacunes. Par exemple, lorsque la concurrence est féroce, elle ne parviendra pas seulement à améliorer l'efficacité, mais réduira l'efficacité, car il existe un processus de mise à niveau de verrouillage supplémentaire. À l'heure actuelle, il est nécessaire de désactiver les verrous biaisés à travers -xx: -UsebiasEd blocking. Voici une comparaison de ces serrures:
Verrouillage | avantage | défaut | Scénarios applicables |
Verrouillage positif | Le verrouillage et le déverrouillage ne nécessitent pas de consommation supplémentaire, et il existe un écart nanoseconde par rapport à la réalisation d'une méthode asynchrone. | S'il y a une concurrence de verrouillage entre les threads, cela entraînera une consommation supplémentaire de révocation de verrouillage. | Convient pour des scénarios où un seul thread accède au bloc synchrone. |
Verrouillage léger | Les fils concurrents ne bloqueront pas, ce qui améliore la vitesse de réponse du programme. | Si le fil qui n'obtient jamais la compétition de verrouillage consommera le CPU. | Poursuivre le temps de réponse. L'exécution de blocs synchrones est très rapide. |
Verrouillage des poids lourds | La concurrence des threads n'utilise pas de spin et ne consomme pas de processeur. | Blocage du fil, temps de réponse lent. | Poursuivant le débit. La vitesse d'exécution du bloc de synchronisation est relativement longue. |