Java Multithreading Basics-15 : Opérations d'optimisation de synchronisation en Java - mise à niveau des verrous, élimination des verrous, grossissement des verrous

D'après un résumé des stratégies de verrouillage courantes en programmation simultanée , synchronisé présente les caractéristiques suivantes :

  1. Cela commence par un verrouillage optimiste, et si des conflits de verrouillage se produisent fréquemment, il est converti en verrouillage pessimiste.
  2. Cela commence par la mise en œuvre d’un verrou léger, et si le verrou est maintenu pendant une longue période, il est converti en un verrou lourd.
  3. Lors de la mise en œuvre de verrous légers, la stratégie de verrouillage rotatif est la plus susceptible d'être utilisée.
  4. C'est un verrouillage injuste.
  5. C'est un verrou rentrant.
  6. Pas un verrou en lecture-écriture.

Cet article présente plusieurs opérations d'optimisation de synchronisation, notamment la mise à niveau des verrous, l'élimination des verrous et le grossissement des verrous.

1. Mise à niveau du verrouillage

JVM divise les verrous synchronisés en quatre états : pas de verrou, verrou biaisé, verrou léger et verrou lourd. Pendant le processus de verrouillage, les mises à niveau seront effectuées séquentiellement en fonction de la situation réelle. (**L'implémentation actuelle de la JVM grand public ne peut verrouiller que les mises à niveau, mais pas les rétrogradations !** Ce n'est pas impossible à mettre en œuvre, mais cela peut être dû au fait qu'il y a certains coûts qui rendent les avantages et les coûts de cette opération disproportionnés, donc cela a n'a pas été mis en œuvre.)

Le processus de verrouillage global (processus de mise à niveau du verrouillage) : lorsque le verrouillage est démarré, il est dans l'état de verrouillage biaisé ; après avoir rencontré une concurrence de verrouillage, il est mis à niveau vers un verrouillage rotatif (verrouillage léger) ; lorsque la concurrence devient plus intense, il deviendra un verrou de niveau de poids (donné au noyau pour qu'il bloque et attende).

1. Verrouillage biaisé

Le premier thread qui tente de se verrouiller entre en premier dans l’état de verrouillage biaisé . Le verrouillage biaisé est une technologie d'optimisation utilisée dans la machine virtuelle Java (JVM) pour améliorer les performances de synchronisation des threads. Dans un environnement multithread, les opérations synchronisées sur les ressources partagées nécessitent l'utilisation de verrous (synchronisés) pour garantir un accès mutuellement exclusif aux threads. Le mécanisme de verrouillage traditionnel entraîne des frais généraux liés à la concurrence et au changement de contexte, ce qui aura un certain impact sur les performances. Des écluses biaisées ont été introduites pour réduire le coût des opérations d'éclusage en l'absence de concurrence.

Le verrouillage biaisé n'est pas vraiment un "verrouillage". Il permet simplement au thread de marquer d'abord l'objet verrou et d'enregistrer à quel thread appartient un certain verrou.

L'idée de base est que lorsqu'un thread acquiert un verrou et accède à un bloc de code synchronisé, s'il n'y a pas de concurrence, la prochaine fois que le thread entre à nouveau dans le bloc synchronisé, il n'a pas besoin d'acquérir à nouveau le verrou. En effet, en l'absence de concurrence, en supposant qu'un thread accède de manière répétée au bloc de code synchronisé, il n'est pas nécessaire de rivaliser pour le verrou à chaque fois. Il lui suffit de déterminer si le verrou est dans un état biaisé ; si c'est le cas, alors directement entrez le bloc de code synchronisé.

En termes simples, si aucun autre thread ne rivalise pour le verrou à l'avenir, il n'est alors pas nécessaire de verrouiller réellement, évitant ainsi la surcharge de verrouillage et de déverrouillage. Mais une fois que d'autres threads tentent de rivaliser pour ce verrou, le verrou biaisé est immédiatement mis à niveau vers un véritable verrou (verrou léger), et les autres threads ne peuvent qu'attendre. Cela garantit à la fois l’efficacité et la sécurité des threads.

Comment déterminer s’il existe d’autres threads en compétition pour le verrou ?

Notez que le verrouillage de biais est un travail effectué en interne de manière synchronisée. synchronisé verrouillera un objet . Ce soi-disant "verrouillage biaisé" fait une marque dans cet objet.

Étant donné que le thread auquel appartient le verrou actuel a été enregistré dans l'objet verrou au début, il est facile d'identifier si le thread demandant actuellement le verrou est le thread enregistré au début.

Si un autre thread essaie de verrouiller le même objet, il essaiera également de le marquer en premier, pour constater qu'il est déjà marqué. Ainsi, la JVM informera le thread qui vient en premier et lui permettra de mettre à niveau le verrou rapidement.

Le verrouillage biaisé est essentiellement un "verrouillage retardé", c'est-à-dire qu'il n'est pas verrouillé s'il peut être déverrouillé, et un verrouillage inutile est évité autant que possible ; cependant, les marquages ​​qui doivent être faits doivent être faits, sinon cela sera impossible pour distinguer quand un véritable verrouillage est nécessaire. .

Donnez un exemple pour comprendre le verrouillage des biais

Supposons que le protagoniste masculin soit une serrure et que la protagoniste féminine soit un fil. Si seuls le protagoniste féminin et le protagoniste masculin sont ambigus (c'est-à-dire que seul ce fil utilise ce verrou), alors même si le protagoniste masculin et le protagoniste féminin n'obtiennent pas de licence pour se marier (évitant des opérations coûteuses), ils peuvent continuer à vivre ensemble.

Mais si une partenaire féminine apparaît à ce moment-là et essaie de rivaliser avec le protagoniste masculin et veut avoir une liaison avec le protagoniste masculin, alors le protagoniste féminin doit prendre une décision immédiate à ce moment-là. le certificat de mariage est, il est voué à être complété (c'est-à-dire que le processus lui-même est terminé) Verrouillage), ce qui fait abandonner la protagoniste féminine.

Par conséquent, verrouillage biaisé = s'engager dans l'ambiguïté ~~

2. Verrouillage de la rotation

**Qu'est-ce qu'un verrou tournant ? ** Mentionné dans l'article sur la stratégie de verrouillage :

Spin Lock est une implémentation typique de verrouillage léger. Il s'agit généralement d'un mode purement utilisateur et ne nécessite pas de passer par le mode noyau. Selon la méthode précédente, une fois que le thread n'a pas réussi à saisir le verrou, il entre dans l'état de blocage et abandonne le processeur. Il faut beaucoup de temps avant de pouvoir être à nouveau planifié. Mais en fait, dans la plupart des cas, même si la capture de verrouillage actuelle échoue, le verrou sera bientôt libéré et il n'est pas nécessaire d'abandonner le processeur. À l’heure actuelle, vous pouvez utiliser les verrous rotatifs pour résoudre de tels problèmes.

Spin Lock est un mécanisme de verrouillage en attente occupé. Lorsqu'un thread doit acquérir un verrou tournant, il vérifiera à plusieurs reprises si le verrou est disponible au lieu d'être bloqué immédiatement. Si l'acquisition du verrou échoue (le verrou est déjà occupé par un autre thread), le thread actuel tentera immédiatement d'acquérir à nouveau le verrou et continuera à tourner (inactif) en attendant que le verrou soit libéré jusqu'à ce que le verrou soit acquis. La première tentative d’acquisition du verrou échoue et la deuxième tentative surviendra dans un délai très court. Cela garantit qu'une fois le verrou libéré par d'autres threads, le thread actuel peut obtenir le verrou dès que possible.

Avantages : aucun processeur n'est abandonné, aucun blocage de thread ni planification n'est impliqué. Une fois le verrou libéré, le verrou peut être obtenu dans les plus brefs délais.
Inconvénients : si le verrou est détenu par d'autres threads pendant une longue période, les ressources CPU continueront à être consommées (attente occupée), mais les ressources CPU ne seront pas consommées lorsqu'elles seront suspendues et en attente.

Les verrous rotatifs conviennent aux situations dans lesquelles la section critique de protection est petite et le verrou occupe peu de temps, car la rotation consomme des ressources CPU. Les verrous rotatifs sont généralement implémentés à l’aide d’opérations atomiques ou d’instructions matérielles spéciales.

Au fur et à mesure que d'autres threads entrent en compétition de verrouillage, l'état de verrouillage biaisé sera éliminé et l'état de verrouillage léger entrera, c'est-à-dire un verrouillage de rotation adaptatif.

Le verrou léger ici est implémenté via CAS. Vérifiez et mettez à jour un morceau de mémoire via CAS (par exemple, en comparant si null est égal à la référence du thread). Si la mise à jour réussit, le verrou est considéré comme réussi ; si la mise à jour échoue, le verrou est considéré comme occupé et le l'attente en forme de rotation continue sans abandonner pendant le processus.Ressources CPU.

(Voir l'explication détaillée de l'algorithme CAS )

Le principe de l'algorithme CAS pour implémenter le spin lock

Étant donné que l'opération de rotation maintient le processeur inactif, ce qui constitue un gaspillage de ressources CPU, la rotation ici ne continuera pas éternellement, mais cessera de tourner après avoir atteint un certain temps ou un certain nombre de tentatives. Ceci est également appelé « adaptatif ».

3. Serrure lourde

**Qu'est-ce qu'un cadenas lourd ? ** Mentionné dans l'article sur la stratégie de verrouillage :

En termes simples, les verrous légers sont une stratégie de verrouillage qui rend le processus de verrouillage et de déverrouillage plus rapide et plus efficace, tandis que les verrous lourds sont une stratégie de verrouillage qui rend le processus de verrouillage et de déverrouillage plus lent et plus efficace. Le mécanisme de verrouillage des verrous lourds repose en grande partie sur le mutex (mutex) fourni par le système d'exploitation.

  • Un grand nombre de commutations de mode utilisateur en mode noyau.
  • Il est facile de provoquer une planification de threads.

Le coût de ces deux opérations est relativement élevé, et une fois qu'il s'agit de basculer entre le mode utilisateur et le mode noyau, l'efficacité est faible.

Si la compétition devient plus intense, la rotation ne peut pas acquérir rapidement l'état de verrouillage. Il se transformera en un verrou lourd.

Bien que le verrou tournant puisse obtenir le verrou le plus rapidement, il consomme beaucoup de CPU (car le processeur tourne rapidement au ralenti pendant la rotation). Si la concurrence actuelle en matière de verrouillage est très féroce, par exemple, 50 threads se disputent un verrou, 1 thread est en compétition pour celui-ci et les 49 autres attendent. Avec autant de threads en rotation et inactifs, la consommation du processeur est très élevée. Dans ce cas, modifiez la stratégie de verrouillage et mettez-la à niveau vers un verrou lourd, permettant aux autres threads de se bloquer et d'attendre dans le noyau (cela signifie que le thread doit temporairement abandonner les ressources CPU et le noyau effectuera une planification ultérieure).

(PS : les systèmes d'exploitation courants actuels tels que Windows et Linux ont une surcharge de planification élevée. Le système ne promet pas de terminer la planification spécifiée dans un délai de xx. Dans les cas extrêmes, la surcharge de planification peut être très importante.

Mais il existe un autre système d'exploitation en temps réel (comme vxworks), qui peut effectuer la planification des tâches à moindre coût, mais sacrifie davantage d'autres fonctions. Il est utilisé dans des domaines particuliers tels que les lancements de fusées, où la précision temporelle est relativement élevée. )

Si la compétition devient plus intense, la rotation ne peut pas acquérir rapidement l'état de verrouillage. Il se transformera en un verrou lourd.

Le verrou lourd fait ici référence au mutex fourni par le noyau.

  1. Lorsqu'un thread effectue une opération de verrouillage, il entre d'abord dans l'état du noyau.
  2. Déterminez si le verrou actuel est déjà occupé par un autre thread en mode noyau.
  3. Si la serrure n'est pas occupée, le verrouillage est réussi et le mode utilisateur repasse en mode utilisateur.
  4. Si la serrure est occupée, la serrure échoue. À ce moment-là, le thread entre dans la file d'attente de verrouillage et se bloque, attendant d'être réveillé par le système d'exploitation.
  5. Après une série de "vicissitudes de la vie", le verrou a finalement été libéré par d'autres threads. À ce moment-là, le système d'exploitation s'est également souvenu du thread suspendu, il a donc réveillé le thread et l'a laissé essayer de réacquérir le verrou.

2. Élimination du verrouillage

L'élimination du verrouillage est également une manifestation de « inutile, pas de verrouillage ». Différente de la mise à niveau du verrouillage, la mise à niveau du verrouillage est une méthode d'optimisation réalisée par la JVM pendant la phase d'exécution du programme. L'élimination des verrous est une méthode d'optimisation lors de la phase de compilation du programme. Le compilateur et la JVM détecteront si le code actuel est exécuté dans plusieurs threads ou si un verrouillage est nécessaire. Si cela n'est pas nécessaire mais que le verrou est écrit, le verrou sera automatiquement supprimé lors du processus de compilation.

Certains codes d'application peuvent être synchronisés inutilement. Par exemple, StringBuffer est thread-safe et chacune de ses méthodes clés comporte le mot-clé synchronisé ajouté :

Une partie du code source de StringBuffer

Mais il y a un problème ici : si StringBuffer est utilisé dans un seul thread, les problèmes de sécurité des threads ne sont pas impliqués. Il n’est en fait pas nécessaire de verrouiller pour le moment. Ensuite, le compilateur agira à ce moment-là et constatera que synchronisé n'est pas nécessaire. Il supprimera synchronisé pendant la phase de compilation, ce qui équivaut à ce que l'opération de verrouillage ne soit pas réellement compilée.

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");

À ce stade, chaque appel à ajouter implique le verrouillage et le déverrouillage. Mais si ce code n'est exécuté que dans un seul thread, ces opérations de verrouillage et de déverrouillage sont inutiles et une certaine surcharge de ressources est gaspillée.

L'élimination des verrous est généralement une méthode d'optimisation relativement conservatrice.Après tout, le compilateur doit s'assurer que l'opération d'élimination est fiable. Par conséquent, l'élimination du verrouillage ne sera mise en œuvre que lorsqu'elle est absolument certaine, sinon elle sera toujours verrouillée.À ce stade, d'autres stratégies opérationnelles seront utilisées pour optimiser le verrouillage (comme la mise à niveau du verrouillage ci-dessus).

3. Verrouillage de la rugosité

La granularité du verrou fait référence à la quantité de code contenue dans le bloc de code synchronisé. Plus il y a de code, plus la granularité est grande ; moins il y a de code, plus la granularité est petite.

Généralement, lorsque nous écrivons du code, nous souhaitons dans la plupart des cas que la granularité du verrouillage soit plus petite. (Une petite granularité de verrouillage signifie que moins de code est exécuté en série et plus de code est exécuté simultanément). Si un scénario nécessite un verrouillage et un déverrouillage fréquents, le compilateur peut optimiser cette opération en un verrou à granularité plus grossière, c'est-à-dire un verrouillage plus grossier.

Dans le processus de développement actuel, des verrous à granularité fine sont utilisés dans l'espoir que d'autres threads puissent utiliser le verrou une fois le verrou libéré. Mais en réalité, il n’y a peut-être pas d’autres threads pour saisir ce verrou. Dans ce cas, la JVM rendra automatiquement le verrou plus grossier pour éviter une surcharge inutile causée par une application et une libération fréquentes des verrous.

Donnez une châtaigne pour comprendre le grossissement de la serrure

Signalez le travail au chef lorsque vous vous rendez au travail. Votre chef vous a assigné trois tâches : A, B et C.
Les méthodes de reporting comprennent :

  1. Passez d'abord un appel téléphonique, signalez l'avancement du travail A et raccrochez ; passez un autre appel téléphonique, signalez l'avancement du travail B et raccrochez ; passez un autre appel téléphonique, signalez l'avancement du travail C et raccrochez. le téléphone. (Vous appelez le leader, et le leader répond à votre appel, et le leader ne peut rien faire d'autre ; si d'autres veulent appeler le leader, ils ne peuvent que bloquer et attendre. Chaque compétition de verrouillage peut introduire une certaine quantité de temps d'attente. À l'heure actuelle, l'efficacité globale peut être encore plus faible.)
  2. Passez un appel téléphonique, signalez le travail A, le travail B et le travail C d'un seul coup, puis raccrochez.

La deuxième méthode est évidemment plus efficace.

On voit que la stratégie synchronisée est relativement complexe, et c'est un verrou très « intelligent ».

Je suppose que tu aimes

Origine blog.csdn.net/wyd_333/article/details/131817841
conseillé
Classement