Mise à niveau des verrous synchronisés en Java (verrous biaisés, verrous légers et verrous lourds)

Points de pré-connaissance du verrouillage de synchronisation Java

  1. Si vous utilisez des verrous dans le codage, vous pouvez utiliser le mot-clé synchronized pour verrouiller les méthodes et les blocs de code de manière synchrone
  2. Le verrou de synchronisation synchronisé est un verrou implicite intégré de jvm (relatif à Lock, implicit lock et release)
  3. La réalisation du verrou de synchronisation synchronisée dépend du système d'exploitation.L'acquisition et la libération du verrou pour les appels système entraîneront le basculement du mode utilisateur et du mode noyau.
  4. Avant jdk1.5, le verrouillage ne peut être utilisé que synchronisé, 1.6 introduit le verrouillage de synchronisation de verrouillage (basé sur l'implémentation java, le verrouillage et la libération explicites, de meilleures performances)
  5. jdk1.6 met en avant le concept de verrouillage biaisé, de verrouillage léger et de verrouillage lourd pour le verrouillage de synchronisation synchronisé (en fait, il optimise les performances de synchronisé et minimise le changement de contexte causé par la concurrence de verrouillage)
  6. Que vous utilisiez synchronisé ou verrouillé, le changement de contexte de thread est inévitable
  7. L'une des optimisations des performances de Lock par rapport à synchronized est: lorsque le thread est bloqué, Lock acquérant le verrou ne provoquera pas le basculement entre le mode utilisateur et le mode noyau, tandis que la synchronisation le sera (voir point 3). Mais le blocage de thread conduira à un changement de contexte (voir point 6)
  8. Le blocage et le réveil des threads java dépendent des appels du système d'exploitation, ce qui entraîne le basculement entre le mode utilisateur et le mode noyau
  9. Le basculement entre le mode utilisateur et le mode noyau mentionné précédemment est un changement de contexte de processus plutôt qu'un changement de contexte de thread.

Cet article se concentre sur la mise à niveau des verrous synchronisés.

verrouillage de synchronisation synchronisé

en-tête d'objet java

Chaque objet java a un en-tête d'objet, qui est composé d'un pointeur de type et d'un champ de balise. Dans une machine virtuelle 64 bits, le pointeur compressé n'est pas activé, le champ de balise occupe 64 bits et le pointeur de type occupe 64 bits, totalisant 16 octets. Les informations sur le type de verrou sont les 2 derniers chiffres du champ d'étiquette: 00 signifie un verrou léger, 01 signifie pas de verrou ou verrou biaisé, 10 signifie verrou lourd; si les 3 chiffres inférieurs sont 1, cela signifie que ce type de verrou biaisé est activé , qui vaut 0 Indique que le verrouillage de polarisation de la classe est désactivé. Comme le montre l'image ci-dessous, l'image provient du wiki: https://wiki.openjdk.java.net/display/HotSpot/Synchronization

La colonne de gauche indique que le verrouillage de polarisation est activé (case 1) et la colonne de droite indique que le verrouillage de polarisation est désactivé (case 3). 1 et 3 représentent tous deux l'état initial d'absence de verrouillage. Si le verrouillage de polarisation est activé, l'étape de mise à niveau du verrouillage doit être 1-> 2-> 4-> 5, si le verrouillage de polarisation est désactivé, l'étape de mise à niveau de verrouillage est 3- > 4-> 5.

J'ai utilisé jdk8. J'ai imprimé les paramètres et je l'ai regardé. Par défaut, le verrouillage de polarisation est activé. S'il est désactivé: -XX:-UseBiasedLocking

Il existe plusieurs autres paramètres concernant le verrouillage de polarisation:

Faites attention au paramètre BiasLockingStartupDelay, la valeur par défaut est de 4000 ms, ce qui signifie que la machine virtuelle démarre avec un délai de 4 s avant d'utiliser le verrou biaisé (utilisez d'abord le verrou léger).

Verrouillage de polarisation

Le scénario du traitement de verrouillage biaisé est que la plupart du temps, seul le même thread demande un verrou, et il n'y a aucune situation où plusieurs threads sont en concurrence pour les verrous. Regardez la case rouge 2 de l'en-tête de l'objet, il y a un champ ID de thread: lorsque le thread est verrouillé pour la première fois, jvm définit l'adresse de thread actuelle sur l'indicateur d'ID de thread via cas, et les 3 derniers bits sont 101. La prochaine fois que le même thread acquiert le verrou, vérifiez uniquement si les 3 derniers chiffres sont 101, s'il s'agit du thread actuel, si l'époque est égale à l'époque de la classe d'objet de verrouillage (le wiki dit qu'il n'y a pas de paramètre cas encore une fois pour l'optimisation multiprocesseur actuelle du fonctionnement du cas).

L'amélioration des performances apportée par l'optimisation du verrouillage de biais fait référence à l'évitement du changement de mode utilisateur et de mode noyau causé par l'appel système du verrou, car le même thread obtient le verrou et il n'est pas nécessaire de faire un appel système à chaque fois le verrou est acquis.

Si l'ID de thread ne correspond pas au thread actuel lorsque le thread actuel acquiert le verrou (dans l'état déverrouillé), le verrou biaisé sera révoqué et à nouveau biaisé vers le thread actuel. Si le nombre de fois atteint la valeur de BiasLockingBulkRebiasThreshold, le la valeur par défaut est 20 fois et le verrou biaisé de la classe actuelle devient invalide. Cela a pour conséquence que la valeur d'époque change, la valeur d'époque de la classe verrouillée est augmentée de 1 et l'objet de verrouillage suivant recopiera la valeur d'époque de la classe au bit de marque d'époque dans la figure. Si le nombre total de révocations atteint la valeur de BiasLockingBulkRevokeThreshold (40 fois par défaut), le verrou biaisé de la classe actuelle est désactivé, qui est la colonne sur le côté droit de l'en-tête de l'objet, et le verrou démarre directement à partir du verrou léger (la serrure est mise à niveau).

La révocation des verrous biaisés est un processus très gênant. Elle nécessite que tous les threads atteignent un point sûr (STW se produit), traverse les piles de threads de tous les threads pour vérifier si l'objet de verrouillage est maintenu, pour éviter la perte de verrou et pour gérer les époques .

S'il existe une concurrence multithread, le blocage du biais a également commencé à s'intensifier.

Serrure légère

Le scénario du traitement de verrouillage léger est que différents threads demandent des verrous à la même période (les threads sont exécutés en alternance). Même si plusieurs threads sont en concurrence pour les verrous à la même période, le thread qui a acquis le verrou maintient le verrou pendant très peu de temps et le verrou est bientôt libéré.

Lorsque le thread est verrouillé, il est jugé qu'il ne s'agit pas d'un verrou lourd et un espace est ouvert dans la pile de threads actuelle. En tant que Li Xiaoran, copiez le champ de balise de l'en-tête de l'objet de verrouillage (la copie consiste à créer un enregistrement, car l'en-tête de l'objet de verrouillage sera copié plus tard). La valeur du champ de balise est remplacée par l'adresse d'espace du champ de balise qui vient d'être copié, tout comme le pointeur pour verrouiller une partie d'enregistrement de l'image dans l'en-tête de l'objet. Comme pour les 2 derniers bits, en raison de l'alignement de la mémoire, il vaut 00). Ensuite, en fonction de l'opération CAS, l'adresse du champ de balise copié est définie sur la valeur du bit de balise de l'en-tête de l'objet de verrouillage. Si elle réussit, le verrou est acquis. S'il est jugé qu'il ne s'agit pas d'un verrou lourd lorsque le verrou est verrouillé et que les deux derniers chiffres ne sont pas 01 (provenant d'un verrou biaisé ou d'un état sans verrou), cela signifie que le thread l'a déjà maintenu. Si le thread actuel est dans (réentrant requis), puis définissez un 0, voici une structure de pile, il suffit de pousser un 0 directement. Lorsque le verrou est finalement libéré, la pile est sautée et le dernier élément enregistre la valeur du champ de balise d'origine de l'objet de verrouillage, qui est ensuite définie sur l'en-tête de l'objet de verrouillage via CAS.

Notez que lors de l'acquisition du verrou, cas échoue, le thread actuel tourne pendant un certain temps, atteignant un certain nombre de fois, mise à niveau vers le verrou lourd, le thread actuel sera également bloqué.

Serrure lourde

Le poids lourd est ce que nous appelons généralement le verrou de synchronisation ajouté, qui est l'implémentation de verrous basés sur Java. Les appels système doivent être effectués lors de l'acquisition et de la libération de verrous, ce qui conduit à un changement de contexte.

À propos du verrouillage de rotation

En ce qui concerne le verrou de rotation, j'ai deux explications principales: 1. C'est un verrou léger qui ne parvient pas à rivaliser, et il ne se développera pas immédiatement en poids lourd mais tournera un certain nombre de fois pour essayer d'acquérir le verrou; 2. Il Si la compétition échoue, elle ne sera pas bloquée immédiatement, et elle tournera un certain nombre de fois (un algorithme d'auto-réglage est ici impliqué). Concernant cette description, cela dépend toujours de l'implémentation source de jvm pour déterminer ce qui est vrai: http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/runtime/synchronizer . cpp

Paramètres du verrouillage du biais d'impression

comme suit:

-XX: + UnlockDiagnosticVMOptions
-XX: + PrintBiasLockingStatistics

J'acquiert le même verrou dans la boucle de méthode principale et le résultat de l'impression est le suivant:

    public static void main(String[] args) {
        int num = 0;
        for (int i = 0; i < 1_000_000000; i++) {
            synchronized (lock) {
                num++;
            }
        }
    }

Je suppose que tu aimes

Origine blog.csdn.net/x763795151/article/details/115059756
conseillé
Classement