Java théorie et la pratique, les algorithmes non bloquants Présentation

Regardez, pas de lock-out!

Lorsque plus d'un fil un accès mutuellement exclusif à une variable, tous les threads doit être synchronisé, sinon il est possible que certaines choses très mauvais va se passer. Langage Java est le principal moyen de synchroniser le mot-clé synchronisé (également connu sous les verrous internes), exclusion mutuelle pour assurer la mise en uvre des fils de bloc d'action synchronisée qui peut être exécutée plus tard par les autres fils du même voir la protection de verrouillage du bloc synchronisé . Lorsqu'ils sont utilisés correctement dans la serrure intrinsèque peut laisser le programme faire le thread-safe, mais en utilisant la protection de verrouillage court chemin de code et contention de verrouillage de fil fréquent lorsque la serrure pourrait devenir fonctionnement assez lourd.

Dans l'article « atomes populaires », nous avons étudié les variables atomiques, les variables atomiques fournissent lecture atomique - écriture - opérations Modify peuvent être mise à jour en toute sécurité des variables partagées sans utiliser les verrous. Semblable à la variable de mémoire volatile variables atomes sémantiques, mais parce qu'ils peuvent être modifiés d'atomes, ils peuvent être utilisés comme base pour des algorithmes concurrents ne pas utiliser les verrous.

Compteur non bloquant

Listing 1 compteur est thread-safe, mais doit apporter un verrou troublé certains développeurs de coûts de performance. Cependant, le verrou est nécessaire, car même si l'augmentation semble être une seule opération, mais est en fait une version simplifiée de trois opérations distinctes: récupérer les valeurs, en ajoutant 1 à la valeur, la valeur réécrites. (Également besoin de synchroniser la méthode getValue pour faire en sorte que les fils d'appel getValue voir la dernière valeur. Alors que de nombreux développeurs à contrecoeur de se convaincre que ignorer les besoins de verrouillage est acceptable, mais ne tient pas compte des besoins de verrouillage ne sont pas une bonne stratégie.)

Lorsque plusieurs threads demandent simultanément le même verrou, il y aura un gagnant et obtenir les serrures de fil, tandis que d'autres threads sont bloqués. implémentations JVM blocage mode suspendent habituellement thread bloqué, puis, Reprogrammation il. Provoquant ainsi le changement de contexte par rapport à quelques quelques instructions protégées par le verrou, il entraînera un retard considérable.

Listing 1. En utilisant la synchronisation de thread-safe du compteur

public final class contre { valeur = 0 longue privée; à long getValue () {public synchronized La valeur de retour; } incrément public long synchronisé () { retourner ++ valeur; } }

Dans le Listing 2 NonblockingCounter montre un simple algorithmes non-blocage: en utilisant la méthode AtomicInteger de compteur (CAS) compareAndSet (). compareAndSet méthode () spécifiée « Cette variable est mis à jour à la nouvelle valeur, mais si les autres fils après avoir vu cette dernière variable pour modifier sa valeur, alors la mise à jour échouera » (voir « atome pop » obtenir sur les variables atomiques et « comparer et ensemble » pour plus d'explications).

Listing 2. En utilisant l'algorithme non bloquant CAS

public class {NonblockingCounter valeur AtomicInteger privée; getValue () int public { retourner value.get (); } incrément public int () { int v; do { v = value.get (); while (! value.compareAndSet (v, v + 1)); retour v + 1; } }

classes de variables atomiques sont appelés atomes, atomique parce qu'ils fournissent des mises à jour de la référence d'objet numérique et à grains fins, mais dans le sens d'un algorithme de non-blocage des blocs de construction de base, qui sont des atomes. algorithme non bloquant comme sujet de recherche, a plus de 20 ans, mais jusqu'à ce que Java 5.0 semble rendre que possible dans le langage Java.

Les processeurs modernes fournissent des instructions spéciales pour mettre à jour automatiquement les données partagées et peuvent détecter les interférences d'autres threads, et de compareAndSet () sur ces verrou remplacé. (Si vous avez à faire est incrémenter le compteur, puis AtomicInteger fournit une méthode d'augmentation, mais ces méthodes sont basées sur compareAndSet (), par exemple NonblockingCounter.increment ()).

Version non-blocage par rapport à la version à base de blocage présente plusieurs avantages de performance. D'abord, il a été remplacé par la forme native de matériel verrouiller le chemin de code machine virtuelle Java de manière à effectuer la synchronisation à un niveau de granularité plus fine (position de mémoire séparée), les fils peuvent être omis retry immédiatement, sans être reprogrammé en attendant la . réduit la granularité les risques de conflits, pas en mesure de rééchelonner la capacité de nouvelle tentative réduit également le coût de discorde. Même une petite quantité d'opération CAS échec, replanification beaucoup plus rapide que cette méthode est encore en raison de la contention de verrouillage causé.

NonblockingCounter cet exemple peut être un simple bit, mais il démontre une caractéristique fondamentale de tous les algorithmes de non-blocage - algorithmes effectuent quelques étapes à prendre des risques, parce que les CAS savoir en cas d'échec peut refaire. algorithmes non bloquants sont généralement appelés algorithme optimiste, en supposant qu'ils continuent de fonctionner parce qu'il n'y aura pas d'interférence. En cas d'interférence est trouvée, il roulera en arrière et essayer à nouveau. Dans l'exemple du compteur, l'étape d'aventure est incrémentale - il récupère l'ancienne valeur et en ajoutant une aux anciennes valeurs, espère valeur ne changera pas au cours de la date de calcul. Si elle espérances déçues, il récupérera à nouveau la valeur, et essayer de faire des calculs supplémentaires.

pile non bloquante

Légèrement exemple plus complexe est l'algorithme non bloquant dans le Listing 3 ConcurrentStack. ConcurrentStack la poussée () et le fonctionnement pop () est une structure similaire à la NonblockingCounter, juste faire le travail d'un certain risque calculé que lorsque le travail « Soumettre », l'hypothèse sous-jacente qu'il n'y a pas d'échec. push () méthode pour observer le nud supérieur le plus courant, la construction d'un nouveau nud sur la pile, puis, si le nud supérieur n'est pas changé depuis l'observation initiale, puis installez le nouveau nud. Si le CAS échoue, cela signifie un autre thread a modifié la pile, le processus recommencera.

Listing 3. En utilisant la pile non-blocage algorithme Treiber

ConcurrentStack public class < E >  { AtomicReference < nud < E > >  tête = new AtomicReference < nud < E > > (); public void poussée (E article) { nud < E >  newHead = nouveau noeud < E > (Point); nud < E >  oldHead; do { oldHead = head.get (); newHead.next = oldHead; } While (head.compareAndSet (oldHead, newHead)!); } pop publique E () { nud < E >  oldHead; nud < E >  newHead; do { oldHead = head.get (); if (oldHead == null) return null; newHead = oldHead.next; } While (head.compareAndSet (oldHead, newHead)!); retourner oldHead.item; } Noeud de classe statique < E >  { point final E; nud < E >  suivant; publique Node (E item) {this.item = item;} } }

Considérations sur les performances

En cas légers à modérés de contention, la performance non-blocage de l'algorithme va au-delà de blocage algorithme, parce que la plupart du temps, le CAS dans la première tentative est couronnée de succès, et les frais généraux de discorde avec un fil se bloque et ne comporte pas le changement de contexte, à seulement quelques itérations de la boucle. Aucune affirmation CAS sans contention de verrouillage que le beaucoup moins cher (cette affirmation est certainement vrai, parce qu'il n'y a pas de conflit de verrouillage implique CAS, plus un traitement supplémentaire), et le ratio de contention CAS contention de verrouillage acquisition impliquant des délais plus courts.

Dans le cas de discorde très (c.-à, il y a plusieurs threads continuent à faire face lorsqu'un emplacement de mémoire), a commencé à fournir un meilleur débit que les algorithmes basés verrouillage algorithme non bloquant, parce que quand le fil est bloqué, il arrêtera le combat avec, en attendant patiemment leur tour, afin d'éviter plus contention. Cependant, un tel degré élevé de discorde n'est pas commun, parce que la plupart du temps, le fil enfilera local calcul et contention pour les données partagées traitées séparément afin d'utiliser la possibilité de partager des données à d'autres threads. (Avec un haut degré de contention tel indiquent également la nécessité d'un algorithme de revérifier, de travailler à moins de données partagées.) « Pop atome » dans le graphique à cet égard est un peu déroutant, étant donné que l'apparition du programme de mesure l'affirmation est extrêmement dense, semble même pour un petit nombre de threads, le verrouillage est une meilleure solution.

Liste non-blocage

Des exemples à ce jour (compteur et pile) sont très simples algorithmes de non-blocage, une fois maîtrisé l'utilisation du CAS dans une boucle, vous pouvez facilement les imiter. Pour les structures de données plus complexes, ces algorithmes non bloquants plus complexes qu'un exemple simple, car la liste modifiée, un arbre ou des mises à jour de la table de hachage peuvent comporter une pluralité de pointeurs. CAS met à jour pour soutenir un pointeur unique conditions atomiques, mais ne supporte pas plus de deux pointeurs. Par conséquent, pour construire une liste non bloquante, arbre ou table de hachage, nous avons besoin de trouver un moyen, vous pouvez mettre à jour plusieurs pointeurs avec CAS, et ne laisserons pas les structures de données dans un état incohérent.

Insérez l'extrémité de la liste des éléments, implique généralement la mise à jour deux pointeurs: le pointeur « queue » toujours des points au dernier élément de la liste, le pointeur « suivant » du dernier élément du point passé à l'élément nouvellement inséré. En raison de la nécessité de mettre à jour deux pointeurs, nous avons besoin de deux CAS. pointeurs de mise à jour dans deux CAS séparés en deux mis les problèmes potentiels à considérer: Si le premier CAS réussit, alors que le second CAS échoue, ce qui se passe? Si d'autres discussions entre la première et la deuxième tentative de CAS d'accéder à la liste, ce qui se passerait?

Pour les structures de données non complexes, les algorithmes construire non-blocage « truc » est de faire en sorte que la structure de données est toujours dans un état cohérent (et même commencer à modifier la structure de données dans le fil et il termine entre la modification), mais aussi de veiller à ce que d'autres threads peuvent être jugés non seulement sur un fil a terminé ou mis à jour au milieu de la mise à jour, mais aussi en mesure de déterminer si le premier fil aller AWOL, quelles actions doivent compléter la mise à jour. Si le fil se trouve au milieu de la mise à jour de la structure de données, il peut mettre à jour « aide » est en cours d'exécution pour compléter le fil de mise à jour, puis effectuer leurs propres opérations. Lorsque les premières tentatives de fil à revenir pour terminer leur mise à jour, vous trouverez ne sont plus nécessaires, il peut revenir, parce que le CAS détectera intervention au fil de l'aide (dans ce cas, une intervention constructive).

Cette exigence « aider un voisin », de sorte que la structure de données pour un seul thread des effets de défaillance, est nécessaire. Si le fil est au milieu des structures de données de découverte étant mis à jour par d'autres threads, puis attendre un autre thread pour terminer la mise à jour, donc si d'autres threads au milieu des opérations échouent, ce fil pourrait continuer à attendre pour toujours. Même si le défaut ne se produit pas, cette approche fournira une mauvaise performance, car fil nouvellement arrivé doit abandonner le processeur, entraînant un changement de contexte, ou attendre jusqu'à ce que sa tranche de temps expire (ce qui est pire).

Listing LinkedQueue 4 montre l'insertion Michael Scott-file d'attente non bloquante algorithme, qui est mis en oeuvre par le ConcurrentLinkedQueue:

Listing 4. Insérer bloquante file algorithme Michael-Scott

LinkedQueue public class < E >  { Noeud de classe statique privée < E >  { point final E; finale AtomicReference < nud < E > >  suivant; Noeud (point E, Noeud < E >  suivant) { this.item = item; this.next = new AtomicReference < nud < E > > (Suivant); } } AtomicReference privé < nud < E > >  tête = Nouveau AtomicReference < nud < E > > (Nouveau nud < E > (NULL, NULL)); AtomicReference privé < nud < E > >  = queue tête; mettre public boolean (E item) { nud < E >  newNode = nouveau noeud < E > (Point, null); while (true) { nud < E >  CURTAIL = tail.get (); nud < E >  = résidu curTail.next.get (); si (== CURTAIL tail.get ()) { if (résidu == NULL) / * A * / { if / * (curTail.next.compareAndSet (null, newNode)) C * / { tail.compareAndSet (CURTAIL, newNode) / * D * /; return true; } } Else { tail.compareAndSet (CURTAIL, résidu) / * B * /; } } } } }

Comme beaucoup d'algorithmes de file d'attente, comme une file d'attente vide contient uniquement un faux nud. pointeur toujours la tête de points aux nuds virtuels; pointeur de queue toujours des points à la dernière ou l'avant-dernier nud de nud. La figure 1 illustre deux éléments de la file d'attente a normalement:

Figure 1. Il y a deux éléments de la file d'attente d'état stationnaire

Comme montré dans le Listing, insérer un élément 4 comporte deux pointeur est mis à jour, la mise à jour sont tous deux effectué par le CAS: le dernier lien à partir du noeud de file d'attente actuel (C) sur le nouveau noeud, et déplacer le curseur à la fin d'une nouvelle dernière noeud (D). Si la première étape échoue, l'état de la file d'attente inchangée, insérez le fil continuera à réessayer jusqu'à ce qu'il réussisse. Une fois l'opération réussie, est insérée comme une force, d'autres threads peuvent voir les changements. Aussi besoin de déplacer le pointeur de la queue à l'emplacement du nouveau nud, mais ce travail peut être vu comme un « nettoyage », car à tout fil dans ce cas pourrait déterminer si la nécessité d'un tel nettoyage, mais aussi savoir comment nettoyage.

La file d'attente est toujours dans un de deux états: un état normal (ou un état stationnaire, figures 1 et 3.) Ou un état intermédiaire (figure 2).. Avant et après l'insertion dans la seconde CAS (D) avec succès, la file d'attente est dans un état stationnaire; CAS après la première (C) avec succès, la file d'attente est dans l'état intermédiaire. Au repos, le pointeur de queue au noeud suivant dans le domaine de liaison est toujours nul, et dans l'état intermédiaire, ce champ est non nulle. En comparant tail.next si un fil est nul, vous pouvez déterminer l'état de la file d'attente, de cette façon vous pouvez aider d'autres fils de fil « complets » opérations critiques.

Sur la figure 2. Avant la file d'attente d'insertion d'état intermédiaire, le nouvel élément est inséré après le pointeur de queue est mis à jour

Avant d'insérer un nouvel élément est inséré dans l'opération (A), pour vérifier si la file d'attente se trouve dans un état intermédiaire, comme dans le Listing 4. Si elle est dans un état intermédiaire, il est certain que d'autres fils ont été insérés dans le milieu de l'élément, entre les étapes (C) et (D). Ne pas attendre d'autres threads pour terminer, le thread courant peut « aider » complètement l'opération, le pointeur de la queue est déplacée vers l'avant (B). Le cas échéant, il continuera de vérifier le pointeur de la queue et déplacez le pointeur vers l'avant jusqu'à ce que la file d'attente au repos, ils peuvent démarrer leur propre inséré.

Le premier CAS (C) probablement parce que deux threads accèdent à la concurrence actuelle du dernier élément de la file d'attente échouer, dans ce cas, et aucun changement se produit, perdre le pointeur de la queue fil CAS reload et essayez à nouveau. Si le second CAS (D) échoue, le fil n'a pas besoin d'insérer nouvelle tentative - car un autre thread a son nom (B) à l'étape terminé l'opération!

Figure 3. Une fois que le pointeur de queue est mise à jour, la réallocation de la file d'attente dans un état stationnaire

algorithmes non-blocage dans les coulisses

Si la profondeur JVM et système d'exploitation, vous trouverez des algorithmes non bloquante partout. Garbage collector utilise des algorithmes de non-blocage simultanées et parallèles pour accélérer la collecte des ordures, planificateur utilise des algorithmes non-bloquant pour efficacement les discussions de planification et les processus pour atteindre verrouillage interne. Dans la Mustang (Java 6.0), algorithme SynchronousQueue basé sur la nouvelle serrure pour être la version non bloquante à la place. Peu de développeurs utiliseront directement SynchronousQueue, mais pool de threads Executors.newCachedThreadPool () pour construire des usines utiliser comme une file d'attente de travail. performances d'affichage du cache du pool de threads comparatif Test comparatif, la mise en uvre nouvelle file d'attente synchrone non-blocage fournit la vitesse est presque trois fois la mise en uvre actuelle. Dans les versions ultérieures du Mustang (nom de code Dolphin), il a été prévu d'autres améliorations.

conclusion

algorithmes non bloquant que l'algorithme basé verrouillage est beaucoup plus complexe. Développement de l'algorithme non-bloquant est une formation tout à fait professionnelle, mais aussi que l'algorithme correct est également très difficile. Mais de nombreuses améliorations simultanées dans les performances entre la version Java de l'utilisation d'algorithmes de non-blocage, et la performance simultanée devient de plus en plus importante, dans la version avenir de la plate-forme Java, nous utiliserons plus non-blocage algorithme.

Restreint l'achat du marché immobilier à Suzhou à double mise à niveau de deux mois quatre fois de poids excessif de la réglementation
Précédent
été loisirs poisson pêche à filet Guba profiter de l'air frais
Prochain
Ant et Eclipse améliorer efficacement l'efficacité du déploiement
Typhoon personnes « Weipa » Impact Qinzhou Voyage dans les douches de vent fort
Étudier à l'étranger vaut-il? « 2019 étudiants étrangers talents demandent rapport » vous dire
En plus de 90 jours pour le rapport, vous le savez, est allé en Thaïlande pour les étrangers prend également 24 heures pour signaler?
Nuit recolorer teinture totale de lotus sacré en espèces Yihe abordables
Lire « restaurant » de savoir que les Etats-Unis fondit île thaïlandaise! La première fois qu'un tel jeu Koh Chang
Typhoon « Yu Wei » est toujours au nord-ouest du Yunnan embrouillage, Guangxi Nord tempête de pluie « voile »
Zhang Peng Northern Light Venture Capital: comment communiquer efficacement avec les investisseurs? | E-Club pour créer une variante porte fermée du Club Partager
Le premier lancement en orbite! fusée privée chinoise pour réaliser une percée | espace commercial privé il y a de grands changements
Combien de Voyage en Thaïlande à dépenser? Les billets cuisine tous vous aider à rester assez clair
Le plus grand avantage a été défait peu rumeur? Maintenant, le marché du pétrole à la fin combien le chaos?
Huit Thai alerte Voyage escroquerie