Préface
Quiconque connaît la programmation simultanée Java sait que la règle des événements avant (HB) dans JMM (modèle de mémoire Java), qui définit l'ordre et la visibilité des opérations multithreads Java, empêchant l'impact de la réorganisation du compilateur sur les résultats du programme.
Il y a une règle de "quand avant" dans la langue java. Il s'agit d'une relation d'ordre partielle entre deux opérations définies dans le modèle de mémoire Java. Si l'opération A se produit d'abord en fonctionnement B, cela signifie qu'avant le fonctionnement B, l'impact de l'opération A peut être observé par l'opération B. L'influence comprend la modification de la valeur des variables partagées en mémoire, l'envoi de messages, les méthodes d'appel, etc., ce qui n'a rien à voir avec la séquence d'occurrence dans le temps. Ce principe est particulièrement important. C'est la base principale pour juger s'il y a de la concurrence dans les données et si les threads sont sûrs.
Selon la déclaration officielle:
Lorsqu'une variable est lue par plusieurs threads et écrite par au moins un thread, s'il n'y a pas de relation HB entre les opérations de lecture et d'écriture, des problèmes de course de données se poseront.
Pour s'assurer que le thread qui fonctionne B voit le résultat de l'opération A (que A et B soient dans le même thread), le principe HB doit être satisfait entre A et B, et sinon, cela peut conduire à une réorganisation.
Lorsque la relation HB est manquante, des problèmes de réorganisation peuvent se produire.
Quelles sont les règles pour HB?
Tout le monde le connaît très bien. La plupart des livres et des articles seront introduits. Prenons-le brièvement ici:
Parmi eux, j'ai en gras des règles de livraison, ce qui est crucial. Comment utiliser les règles de livraison avec compétence est la clé pour atteindre la synchronisation.
Ensuite, expliquez HB dans une autre perspective: lorsqu'un opération A HB fonctionne B, le résultat du fonctionnement de l'opération A sur la variable partagée est visible au fonctionnement B.
Dans le même temps, si l'opération B HB fonctionne C, le résultat de l'opération a sur la variable partagée est visible pour l'opération B.
Le principe de la visibilité est le protocole de cache et la barrière de la mémoire. La visibilité est réalisée grâce aux protocoles de cohérence du cache et aux barrières de mémoire.
Comment atteindre la synchronisation?
Dans le livre de Doug Lea "Java concurrence dans la pratique", la description suivante est:
Le livre mentionne: En combinant certaines règles d'Hb, la visibilité d'une variable protégée débloquée peut être obtenue.
Mais parce que cette technique est sensible à l'ordre des déclarations, elle est sujette aux erreurs.
Ensuite, l'auteur montrera comment synchroniser une variable via des règles volatiles et des règles d'ordre du programme.
Ayons un exemple familier:
classe threadprintdemo {static int num = 0; Flag booléen volatile statique = false; public static void main (String [] args) {thread t1 = new Thread (() -> {for (; 100> num;) {if (! flag && (num == 0 || ++ num% 2 == 0)) {system.out.println (num); flag = true;}}}); Thread t2 = new Thread (() -> {for (; 100> num;) {if (flag && (++ num% 2! = 0)) {System.out.println (num); flag = false;}}}); t1.start (); t2.start (); }}Le but de ce code est d'imprimer les numéros 0 - 100 entre deux threads.
Les étudiants qui connaissent la programmation simultanée doivent dire que cette variable num n'utilise pas de volatile, et il y aura des problèmes de visibilité, c'est-à-dire que le thread T1 a mis à jour NUM, et le thread T2 ne peut pas le percevoir.
Haha, l'auteur l'a pensé au début, mais récemment en étudiant les règles HB, j'ai constaté qu'il était acceptable de supprimer la modification volatile de NUM.
Analyons-le et l'affiche a dessiné une image:
Analysons cette figure:
Remarque: La règle HB garantit que les résultats de l'opération précédente sont visibles à l'opération suivante.
Par conséquent, dans l'applet ci-dessus, le thread B est pleinement conscient de la modification de NUM par le thread A - même si Num n'est pas modifié avec un volatile.
De cette façon, nous utilisons le principe HB pour réaliser le fonctionnement synchrone d'une variable, c'est-à-dire dans un environnement multi-thread, nous assurons la sécurité de la modification simultanée des variables partagées. Et il n'y a pas de primitives Java pour cette variable: volatile et synchronisée et CAS (en supposant qu'elle compte).
Cela peut sembler dangereux (en fait sûr) et peut ne pas sembler facile à comprendre. Parce que tout cela est implémenté par le protocole de cache et la barrière de mémoire dans le HB sous-jacent.
Autres règles pour atteindre la synchronisation
Implémentation à l'aide de règles de terminaison de thread:
statique int a = 1; public static void main (String [] args) {thread tb = new Thread (() -> {a = 2;}); Thread ta = new Thread (() -> {try {tb.join ();} catch (interruptedException e) {// no} system.out.println (a);}); ta.start (); tb.start (); } Utilisez des règles de démarrage du thread pour implémenter:
statique int a = 1; public static void main (String [] args) {thread tb = new Thread (() -> {System.out.println (a);}); Thread ta = new Thread (() -> {tb.start (); a = 2;}); ta.start (); }Ces deux opérations peuvent également assurer la visibilité de la variable a.
Il subvertit vraiment le concept précédent. Dans le concept précédent, si une variable n'est pas modifiée par volatile ou finale, sa lecture et son écriture sous le multi-threading sont définitivement dangereuses - car il y aura des caches, dont la lecture n'est pas la dernière.
Cependant, en utilisant HB, nous pouvons y parvenir.
Résumer
Bien que le titre de cet article soit de réaliser des opérations synchrones de variables partagées à travers les cas avant, l'objectif principal est de comprendre plus profondément. La compréhension de son concept en cours est en fait pour garantir que l'ordre de l'opération précédente à l'opération suivante et la visibilité de l'opération se traduit par un environnement multi-thread.
Dans le même temps, en utilisant de manière flexible les règles transitives, puis en combinant des règles, deux threads peuvent être synchronisés - l'implémentation de la variable partagée spécifiée sans utiliser de primitives peut également garantir la visibilité. Bien que cela ne semble pas être très facile à lire, c'est aussi une tentative.
Doug Lea donne une pratique dans le JUC sur la façon de combiner les règles pour atteindre la synchronisation.
Par exemple, la synchronisation de la classe intérieure de l'ancienne version de Futuretask (disparu), modifie la variable volatile à travers la méthode TryReleSeshared, et TryAcQuirsared lit la variable volatile, qui utilise les règles volatiles;
Cela tire parti des règles de commande de programme en définissant une variable de résultat non volatile avant TryReleSeshared, puis en lisant la variable de résultat après TryAcquireShared.
Cela garantit la visibilité de la variable de résultat. Semblable à notre premier exemple: utiliser des règles de commande de programme et des règles volatiles pour atteindre une visibilité variable normale.
Doug Lea lui-même a déclaré que cette technologie "utilisation de l'aide" est très sujette aux erreurs et devrait être utilisée avec prudence. Mais dans certains cas, ce type de «effet de levier» est très raisonnable.
En fait, BlockingQueue "a également" utilisé "des règles en cours. Rappelez-vous la règle de déverrouillage? Lorsque le déverrouillage se produit, les éléments internes doivent être visibles.
Il existe d'autres opérations dans la bibliothèque de classe qui «utilisent» également le principe en cours: conteneurs simultanés, compte à rebours, sémaphore, futur, exécuteur, cyclique, échangeur, etc.
Bref, bref:
Le principe en cours est le cœur de JMM. Ce n'est que lorsque le principe HB est respecté que l'ordre peut être assuré et que la visibilité soit assurée, sinon le compilateur réorganisera le code. HB définit même les règles de verrouillage et volatile.
Par combinaison appropriée de règles HB, l'utilisation correcte des variables partagées ordinaires peut être réalisée.
D'accord, ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.