Dans le développement général, l'auteur voit souvent que de nombreux étudiants n'utilisent que certaines méthodes de base pour traiter le modèle de développement simultané Java. Par exemple, volatile, synchronisé. Les packages simultanés avancés comme Lock et Atomic ne sont pas souvent utilisés par de nombreuses personnes. Je pense que la plupart des raisons sont dues au manque d'attributs au principe. Dans le travail de développement occupé, qui peut saisir et utiliser avec précision le modèle de concurrence correct?
Donc, récemment, sur la base de cette idée, je prévois d'organiser le mécanisme de contrôle de la concurrence dans un article. Ce n'est pas seulement un souvenir de vos propres connaissances, mais espère également que le contenu mentionné dans cet article peut aider la plupart des développeurs.
Le développement de programmes parallèles implique inévitablement des problèmes tels que la collaboration multi-threading et le partage de données multi-tâches. Dans JDK, plusieurs manières sont fournies pour implémenter un contrôle simultané entre plusieurs threads. Par exemple, couramment utilisé: verrouillage interne, verrouillage de rentrée, verrouillage d'écriture en lecture et sémaphore.
Modèle de mémoire Java
En Java, chaque thread a une zone de mémoire de travail, qui stocke une copie de la valeur de la variable dans la mémoire principale partagée par tous les threads. Lorsqu'un thread s'exécute, il exploite ces variables dans sa propre mémoire de travail.
Afin d'accéder à une variable partagée, un thread acquiert généralement le verrou et efface sa zone de mémoire de travail, qui garantit que la variable partagée est correctement chargée à partir de la zone de mémoire partagée de tous les threads dans la zone de mémoire de travail du thread. Lorsque le thread se déverrouille, la valeur de la variable dans la zone de mémoire de travail est garantie d'être associée à la mémoire partagée.
Lorsqu'un thread utilise une certaine variable, que le programme utilise correctement les opérations de synchronisation du thread, la valeur qu'elle obtient doit être la valeur stockée dans la variable par elle-même ou d'autres threads. Par exemple, si deux threads stockent différentes valeurs ou références d'objets dans la même variable partagée, la valeur de la variable est soit à partir de ce thread ou de ce thread, et la valeur de la variable partagée ne sera pas composée des valeurs de référence des deux threads.
Une adresse à laquelle les programmes Java peuvent accéder lorsqu'une variable est utilisée. Il inclut non seulement les variables de type de base et les variables de type de référence, mais également les variables de type de tableau. Les variables stockées dans la zone de mémoire principale peuvent être partagées par tous les threads, mais il est impossible pour un thread d'accéder aux paramètres ou aux variables locales d'un autre thread, de sorte que les développeurs n'ont pas à s'inquiéter des problèmes de sécurité du fil des variables locales.
Les variables volatiles peuvent être vues entre plusieurs threads
Étant donné que chaque thread a sa propre zone de mémoire de travail, elle peut être invisible pour d'autres threads lorsqu'un thread modifie ses propres données de mémoire de travail. Pour ce faire, vous pouvez utiliser le mot-clé volatil pour casser tous les threads pour lire et écrire des variables en mémoire, afin que les variables volatiles soient visibles parmi plusieurs threads.
Les variables déclarées comme volatiles peuvent être garanties comme suit:
1. Les modifications des variables par d'autres threads peuvent être rapidement reflétées dans le thread actuel;
2. Assurez-vous que la modification du thread actuel de la variable volatile peut être réécrite à la mémoire partagée à temps et vue par d'autres threads;
3. Utilisez des variables déclarées par volatile, et le compilateur assurera leur ordre.
Mots-clés synchronisés
Les mots clés synchronisés synchronisés sont l'une des méthodes de synchronisation les plus couramment utilisées dans le langage Java. Dans les premières versions JDK, les performances de Synchronized n'étaient pas très bonnes et la valeur était adaptée aux occasions où la concurrence des verrouillage n'était pas particulièrement féroce. Dans JDK6, l'écart entre les verrous synchronisés et déloyaux s'est rétréci. Plus important encore, la synchronisation est plus concise et plus claire, et le code est lisible et maintenu.
Méthodes pour verrouiller un objet:
Méthode void synchronisée publique () {}
Lorsque la méthode Method () est appelée, le thread d'appel doit d'abord obtenir l'objet actuel. Si le verrouillage d'objet actuel est maintenu par d'autres threads, le fil d'appel attendra. Une fois la violation terminée, le verrouillage de l'objet sera libéré. La méthode ci-dessus est équivalente à la méthode d'écriture suivante:
Méthode public void () {synchronisé (this) {// faire quelque chose…}} Deuxièmement, le synchronisé peut également être utilisé pour construire des blocs de synchronisation. Par rapport aux méthodes de synchronisation, les blocs de synchronisation peuvent contrôler plus précisément la plage de codes de synchronisation. Un petit code de synchronisation est très rapide dans et hors des verrous, donnant ainsi au système un débit plus élevé.
Méthode de void public (objet o) {// beforesynchronized (o) {// faire quelque chose ...} // après} Synchronisé peut également être utilisé pour les fonctions statiques:
Méthode de void statique synchronisée publique () {}
Il est important de noter à cet endroit que le verrou synchronisé est ajouté à l'objet de classe actuel, donc tous les appels vers cette méthode doivent obtenir le verrou de l'objet de classe.
Bien que synchronisé puisse garantir la sécurité du thread des objets ou des segments de code, l'utilisation de synchronisée seule n'est toujours pas suffisante pour contrôler les interactions du thread avec la logique complexe. Afin d'atteindre l'interaction entre plusieurs threads, les méthodes d'attente () et de notifier () de l'objet objet sont également nécessaires.
Utilisation typique:
synchronisé (obj) {while (<?>) {obj.wait (); // Continue à exécuter après avoir reçu la notification. }} Avant d'utiliser la méthode Wait (), vous devez obtenir le verrouillage de l'objet. Lorsque la méthode Wait () est exécutée, le thread actuel peut libérer le verrouillage exclusif d'OBJ pour une utilisation par d'autres threads.
En attendant que le fil sur OBJ reçoive obj.notify (), il peut regagner le verrou exclusif d'OBJ et continuer à fonctionner. Notez que la méthode notify () consiste à évoquer au hasard un thread en attente de l'objet actuel.
Voici une implémentation d'une file d'attente de blocage:
classe publique blockqueue {private list list = new ArrayList (); L'objet synchronisé public pop () lève InterruptedException {while (list.size () == 0) {this.wait (); } if (list.size ()> 0) {return list.Remove (0); } else {return null; }} objet synchronisé public put (objet obj) {list.add (obj); this.notify (); }} Synchronisé et attendre () et notify () doit être une compétence de base que les développeurs Java doivent maîtriser.
Ventrantlock Reentrantlock Lock
Reentrantlock est appelé reentrantlock. Il a des caractéristiques plus puissantes que synchronisées, il peut interrompre et temps. Dans le cas d'une concurrence élevée, il présente des avantages de performance évidents par rapport à la synchronisation.
Reentrantlock fournit des verrous à la fois équitables et déloyaux. Une fermeture équitable est la première sortie de la serrure, et pas une clairière équitable ne peut être coupée en ligne. Bien sûr, du point de vue des performances, les performances des serrures déloyales sont bien meilleures. Par conséquent, en l'absence de besoins spéciaux, les verrous déloyaux doivent être préférés, mais synchronisé fournit à l'industrie du verrouillage n'est pas absolument juste. Reentrantlock peut spécifier si le verrou est équitable lors de la construction.
Lorsque vous utilisez un verrou de rentrée, assurez-vous de libérer le verrou à la fin du programme. Généralement, le code pour libérer le verrou doit être rédigé enfin. Sinon, si l'exception du programme se produit, Loack ne sera jamais publié. Le verrou synchronisé est automatiquement libéré par le JVM à la fin.
L'utilisation classique est la suivante:
essayez {if (lock.trylock (5, timeUnit.seconds)) {// s'il a été verrouillé, essayez d'attendre les 5 pour voir si la serrure peut être obtenue. Si le verrouillage ne peut pas être obtenu après 5s, renvoyez false pour continuer l'exécution // lock.lockinterruptiblement (); peut répondre à l'événement d'interruption essayez {// opération} enfin {lock.unlock (); }}} catch (InterruptedException e) {e.printStackTrace (); // Lorsque le thread actuel est interrompu (interruption), une interruption d'Exception sera lancée}Reentrantlock fournit une riche variété de fonctions de contrôle de verrouillage et applique de manière flexible ces méthodes de contrôle pour améliorer les performances de l'application. Cependant, il n'est pas fortement recommandé d'utiliser Reentrantlock ici. Le verrouillage de retour est un outil de développement avancé fourni dans JDK.
ReadWritelock Read and Write Lock
La séparation de lecture et d'écriture est une idée de traitement des données très courante. Il doit être considéré comme une technologie nécessaire dans SQL. ReadWriteLock est un verrou de séparation en lecture-écriture fournis dans JDK5. Les verrous de séparation de lecture et d'écriture peuvent efficacement aider à réduire la concurrence des verrous pour améliorer les performances du système. Les scénarios d'utilisation de séparation de lecture et d'écriture sont principalement si dans le système, le nombre d'opérations de lecture est beaucoup plus grand que les opérations d'écriture. Comment l'utiliser est comme suit:
private reentrantaDWriteLock readWriteLock = new reentRanTreadWriteLock (); private Lock readlock = readWriteLock.readlock (); private Lock WriteLock = readWriteLock.writeLock (); public objet handleread () lance InterruptedException {try {readLock. Thread.Sleep (1000); valeur de retour; } enfin {readlock.unlock (); }} public Object HandleRead () lève InterruptedException {try {writeLock.lock (); Thread.Sleep (1000); valeur de retour; } enfin {writeLock.unlock (); }} Objet de condition
L'objet ConditionD est utilisé pour coordonner la collaboration complexe entre plusieurs threads. Principalement associé aux verrous. Une instance de condition limitée à verrouillage peut être générée via la méthode NewCondition () dans l'interface de verrouillage. La relation entre un objet de condition et un verrou est comme l'utilisation des deux fonctions objet.wait (), objet.notify () et les mots clés synchronisés.
Ici, vous pouvez extraire le code source de ArrayBlockingQueue:
classe publique ArrayBlockingQueue étend AbstractQueue implémente BlockingQueue, Java.io.Serializable {/ ** MAIN LOCK GARDING ALL ACCES IllégalArgumentException (); this.items = nouvel objet [capacité]; Lock = new ReentrantLock (Fair); NotEmpty = Lock.NewCondition (); // Générer la condition Notfull = Lock.NewCondition ();} public void put (e e) InterruptedException {checkNotNull (e); Ventrantlock final Lock = this.lock; lock.lockInterruply (); try {while (count == items.length) notull.await (); insérer (e); } enfin {lock.unlock (); }} insert privé void (e x) {items [putIndex] = x; puttindex = inc (putIndex); ++ count; NotEmpty.signal (); // Notification} public e prent () lève InterruptedException {final reentrantLock lock = this.lock; lock.lockInterruply (); essayez {while (count == 0) // si la file d'attente est vide notEmpty.Await (); // Ensuite, la file d'attente des consommateurs doit attendre un retour du signal non vide Extract (); } enfin {lock.unlock (); }} private e extract () {objet final [] items = this.items; E x = this. <e> cast (items [takeIndex]); éléments [TakeIndex] = null; TakeIndex = Inc (TakeIndex); --compter; notull.signal (); // informer put () que la file d'attente de thread a un retour d'espace libre x;} // autre code} Semaphore Semaphore <br /> Semaphore fournit une méthode de contrôle plus puissante pour la collaboration multi-thread. Le sémaphore est une extension de la serrure. Qu'il s'agisse du verrou interne synchronisé ou du reentrantlock, un thread permet d'accéder à une ressource à la fois, tandis que le sémaphore peut spécifier que plusieurs threads accèdent à une ressource en même temps. Du constructeur, nous pouvons voir:
Semaphore public (int permets) {}
Semaphore public (INT Permis, Boolean Fair) {} // peut spécifier si c'est juste
Les permis spécifient le livre d'accès pour le sémaphore, ce qui signifie combien de licences peuvent être appliquées en même temps. Lorsque chaque thread s'applique uniquement à une licence à la fois, cela équivaut à spécifier le nombre de threads peut accéder à une certaine ressource en même temps. Voici les principales méthodes à utiliser:
public void Acquire () lève InterruptedException {} // Essayez d'obtenir une autorisation d'accès. S'il n'est pas disponible, le fil attendra, sachant qu'un fil libère une autorisation ou le thread actuel est interrompu.
public void AcquireUnterruply () {} // similaire à acquire (), mais ne répond pas aux interruptions.
public boolean tryacquire () {} // Essayez de l'obtenir, vrai en cas de succès, autrement faux. Cette méthode n'attendra pas et reviendra immédiatement.
Public Boolean Tryacquire (temps de temps long, unité de tempsunit) lève InterruptedException {} // Combien de temps faut-il pour attendre
public void release () // est utilisé pour libérer une licence une fois la ressource d'accès sur site terminée afin que les autres threads en attente de l'autorisation puissent accéder à la ressource.
Jetons un coup d'œil aux exemples d'utilisation des sémaphores fournis dans le document JDK. Cet exemple explique comment contrôler l'accès aux ressources via des sémaphores.
classe publique Pool {private static final int max_available = 100; sémaphore final privé disponible = new Semaphore (max_available, true); objet public getItem () lance InterruptedException {disponible.acquire (); // postuler pour une licence // seuls 100 threads peuvent entrer pour obtenir des articles disponibles en même temps, // Si plus de 100, vous devez attendre le retour getNExtAvailableItem ();} public void Putitem (objet x) {// remettre l'élément donné dans la piscine et le marquer comme non utilisé si (markasunUsed (x)) {disponible.release (); // a ajouté un élément disponible, libérer une licence et le thread demandant la ressource est activé}} // Par exemple, référence uniquement, objet non réel protégé des données [] items = nouvel objet [max_available]; // utilisé pour les objets de multiplexage du pool d'objets protégés booléens [] utilisés = new booléen [max_available]; // Fonction de balisage Protégé objet synchronisé getNextSavailableItem () {for (int i = 0; i <max_available; ++ i) {if (! Utilisé [i]) {utilisé [i] = true; return items [i]; }} return null;} protégé booléen synchronisé markasunUsed (objet élément) {for (int i = 0; i <max_available; ++ i) {if (item == items [i]) {if (used [i]) {used [i] = false; Retour Vrai; } else {return false; }}} return false;}} Cette instance implémente simplement un pool d'objets d'une capacité maximale de 100. Par conséquent, lorsqu'il y a 100 demandes d'objet en même temps, le pool d'objets aura une pénurie de ressources et que les threads qui ne doivent pas obtenir les ressources doivent attendre. Lorsqu'un fil se termine à l'aide d'un objet, il doit renvoyer l'objet dans le pool d'objets. Pour le moment, puisque les ressources disponibles augmentent, un fil en attente de la ressource peut être activé.
Variables locales de thread ThreadLocal <br /> Après avoir commencé à contacter ThreadLocal, il est difficile pour moi de comprendre les scénarios d'utilisation de cette variable locale de thread. En regardant en arrière maintenant, ThreadLocal est une solution pour un accès simultané aux variables entre plusieurs threads. Contrairement aux méthodes de verrouillage synchronisées et autres, ThreadLocal ne fournit pas du tout les verrous, mais utilise la méthode d'échange d'espace pour le temps pour fournir à chaque thread des copies indépendantes des variables pour assurer la sécurité des filetages. Par conséquent, ce n'est pas une solution pour le partage de données.
ThreadLocal est une bonne idée de résoudre les problèmes de sécurité des filetages. Il existe une carte dans la classe ThreadLocal qui stocke une copie des variables pour chaque thread. La clé de l'élément de la carte est un objet de thread, et la valeur correspond à la copie des variables pour le thread. Étant donné que la valeur de clé ne peut pas être répétée, chaque "objet de thread" correspond à la "copie des variables" du thread, et elle atteint la sécurité du fil.
Il est particulièrement remarquable. En termes de performances, ThreadLocal n'a pas de performances absolues. Lorsque le volume de concurrence n'est pas très élevé, les performances du verrouillage seront meilleures. Cependant, en tant qu'ensemble de solutions en filetage qui ne sont pas complètement liées aux verrous, l'utilisation de threadlocal peut réduire la concurrence des verrous dans une certaine mesure dans une concurrence élevée ou féroce.
Voici une utilisation simple du threadlocal:
classe publique TestNum {// Moverment la méthode InitialValue () de ThreadLocal via une classe intérieure anonyme, spécifiez la valeur initiale statique privée seqnum = new ThreadLocal () {public Integer initialValue () {return 0; }}; // Obtenez la valeur de séquence suivante public int getNextnum () {seqnum.set (seqnum.get () + 1); return seqnum.get ();} public static void main (String [] args) {testnum sn = new testNum (); // 3 threads partagent SN, chacun générant un numéro de séquence TestClient t1 = new TestClient (SN); TestClient T2 = nouveau TestClient (SN); TestClient T3 = nouveau TestClient (SN); t1.start (); t2.start (); t3.start (); } Classe statique privée TestClient étend Thread {private testNum sn; public testClient (testNum sn) {this.sn = sn; } public void run () {for (int i = 0; i <3; i ++) {// chaque thread produit 3 valeurs de séquence system.out.println ("thread [" + thread.currentThread (). getName () + "] -> sn [" + sn.getnextNum () + "]"); }}}} Résultat de sortie:
Thread [Thread-0]> Sn [1]
Thread [Thread-1]> sn [1]
Thread [Thread-2]> Sn [1]
Thread [Thread-1]> Sn [2]
Thread [Thread-0]> Sn [2]
Thread [Thread-1]> sn [3]
Thread [Thread-2]> sn [2]
Thread [Thread-0]> Sn [3]
Thread [Thread-2]> sn [3]
Les informations de résultat de sortie peuvent être constatées que, bien que les numéros de séquence générés par chaque thread partagent la même instance de test, ils n'interfèrent pas les uns avec les autres, mais chacun génère des nombres de séquence indépendants. En effet, ThreadLocal fournit une copie distincte pour chaque thread.
Les performances de verrouillage et l'optimisation des «verrous» sont l'une des méthodes de synchronisation les plus couramment utilisées. En développement normal, vous pouvez souvent voir de nombreux étudiants ajouter directement un grand morceau de code à la serrure. Certains étudiants ne peuvent utiliser qu'une seule méthode de verrouillage pour résoudre tous les problèmes de partage. De toute évidence, un tel encodage est inacceptable. En particulier dans les environnements à forte concurrence, la concurrence féroce de verrouillage conduira à une dégradation des performances plus évidente du programme. Par conséquent, l'utilisation rationnelle des verrous est directement liée aux performances du programme.
1. Le thread aérien <br /> dans le cas du multi-core, l'utilisation du multi-threading peut considérablement améliorer les performances du système. Cependant, dans les situations réelles, l'utilisation de multi-threading ajoutera des frais généraux supplémentaires. En plus de la consommation de ressources des tâches de système monocœur elles-mêmes, les applications multithreades doivent également maintenir des informations uniques multiples supplémentaires. Par exemple, les métadonnées du fil lui-même, la planification du thread, la commutation de contexte de thread, etc.
2. Réduisez le temps de maintien de verrouillage
Dans les programmes qui utilisent des verrous pour un contrôle simultané, lorsque les verrous rivalisent, le temps de maintien de verrouillage d'un seul thread a une relation directe avec les performances du système. Si le fil maintient la serrure pendant longtemps, la compétition pour la serrure sera plus intense. Par conséquent, au cours du processus de développement du programme, le délai d'occupation d'une certaine serrure doit être minimisé pour réduire la possibilité d'exclusion mutuelle entre les threads. Par exemple, le code suivant:
public synchronisé void syncMehod () {beForeMethod (); mutexMethod (); AfterMethod ();} Si seule la méthode MuteXMethod () dans ce cas est synchrone, mais dans beForeMethod () et AfterMethod () ne nécessite pas de contrôle de synchronisation. Si BeforeMethod () et AfterMethod () sont des méthodes de poids lourds, cela prendra beaucoup de temps pour le CPU. Pour le moment, si la concurrence est importante, l'utilisation de ce schéma de synchronisation entraînera une forte augmentation des fils d'attente. Étant donné que le thread en cours d'exécution ne libèrera le verrou que une fois que toutes les tâches ont été exécutées.
Ce qui suit est une solution optimisée, qui ne se synchronise que lorsque cela est nécessaire, de sorte que le temps pour que les filetages maintiennent des verrous puissent être considérablement réduits et que le débit du système peut être amélioré. Le code est le suivant:
public void syncmehod () {beForeMethod (); synchronisé (this) {mutexMethod ();} afterMethod ();} 3. Réduire la taille des particules de verrouillage
La réduction de la granularité des serrures est également un moyen efficace d'affaiblir la concurrence pour les serrures multi-thread. Le scénario d'utilisation typique de cette technologie est la classe concurrentehashmap. Dans HashMap ordinaire, chaque fois qu'une opération ADD () ou l'opération GET () est effectuée sur une collection, le verrouillage de l'objet de collection est toujours obtenu. Cette opération est complètement un comportement synchrone car le verrou est sur l'ensemble de l'objet de collecte. Par conséquent, dans une concurrence élevée, la concurrence féroce de verrouillage affectera le débit du système.
Si vous avez lu le code source, vous devez savoir que HashMap est implémenté dans une liste Array + Linked. ConcurrenthashMap divise l'intégralité de hashmap en plusieurs segments (segments), et chaque segment est un sous-hachap. Si vous devez ajouter une nouvelle entrée de table, vous ne verrouillez pas le hashmap. La ligne de recherche de vingt ans obtiendra la section dans laquelle l'entrée du tableau doit être stockée en fonction du HashCode, puis verrouiller la section et terminer l'opération put (). De cette façon, dans un environnement multithread, si plusieurs threads effectuent des opérations d'écriture en même temps, tant que l'élément écrit n'existe pas dans le même segment, le vrai parallélisme peut être réalisé entre les threads. Pour une implémentation spécifique, j'espère que les lecteurs prendront le temps de lire le code source de la classe concurrentehashmap, donc je ne le décrirai pas trop ici.
4. Séparation de verrouillage <br /> Une lecture de lecture et d'écriture de lien de lecture mentionnée précédemment, alors l'extension de la séparation de lecture et d'écriture est la séparation du verrouillage. Le code source de séparation de verrouillage se trouve également dans le JDK.
classe publique LinkedBlockingQueue étend Résumé Résumétque out les implémentés BlockingQueue, java.io.serializable {/ * verrouillage Hold by Take, Poll, etc / Private Final RentrantLock Takelock = Nouveau ReentrantLock (); / ** Wait Fitre pour l'attente PAPE * / Private Final Condition NotEmpty = TakeLock.NewCondition () new reentrantLock (); / ** attendre la file d'attente pour les put d'attente * / condition finale privée notull = putlock.newCondition (); public e pren () lance InterruptedException {ex; int c = -1; Count d'atomicinteger final = this.Count; rentrantlock final takelock = this.Takelock; takelock.lockinterruptiblement (); // il ne peut pas y avoir deux threads pour lire les données en même temps essayez {while (count.get () == 0) {// s'il n'y a pas de données disponibles, attendez la notification de put () notEmpty.Await (); } x = dequeue (); // supprime un élément c = count.getAndDecment (); // taille moins 1 if (c> 1) notEmpty.signal (); // notifier d'autres opérations TAPT ()} enfin {takelock.unlock (); // Libérez le verrouillage} if (c == capacine) signalNotfull (); // notifier le fonctionnement put (), il y a déjà un retour d'espace libre x;} public void put (e e) lance InterruptedException {if (e == null) lancez new nullpointerException (); // Remarque: la convention dans tous les put / take / etc est de prédéfinir Var // le nombre de comptages négatifs pour indiquer une défaillance, sauf si c'est défini. int c = -1; Nœud <e> node = new nœud (e); reentrantlock final putlock = this.putlock; Count d'atomicinteger final = this.Count; putlock.lockInterruply (); // Il ne peut pas y avoir deux threads mettent les données en même temps, essayez {/ * * Notez que le nombre est utilisé dans le garde d'attente même s'il n'est * pas protégé par le verrouillage. Cela fonctionne parce que le nombre ne peut * diminuer qu'à ce stade (tous les autres put sont arrêtés * par verrouillage), et nous (ou une autre put d'attente) * sommes signés s'il change de capacité. De même * pour toutes les autres utilisations du nombre dans d'autres gardes d'attente. * / while (count.get () == Capacité) {// Si la file d'attente est pleine, attendez Notfull.Await (); } enqueue (nœud); // Rejoignez la file d'attente C = count.getAndIncrement (); // taille plus 1 if (c + 1 <capacile) notull.signal (); // informer les autres threads s'il y a suffisamment d'espace} enfin {putlock.unlock (); // relâchez le verrou} if (c == 0) signalNotempty (); // Une fois l'insertion réussie, notifiez l'opération TAP () pour lire les données} // autre code}Ce qui doit être expliqué ici, c'est que les fonctions prennent () et put () sont indépendantes les unes des autres, et il n'y a pas de relation de concurrence de verrouillage entre eux. Il vous suffit de rivaliser pour Takelock et Putlock dans les méthodes respectives de Take () et put (). Ainsi, la possibilité d'une compétition de verrouillage est affaiblie.
5. La grossièreté de verrouillage <Br /> La réduction mentionnée ci-dessus du temps de verrouillage et de la granularité est effectuée pour répondre aux plus courtes pour chaque fil pour maintenir la serrure. Cependant, un degré doit être saisi dans la granularité. Si une serrure est constamment demandée, synchronisée et libérée, elle consommera des ressources précieuses du système et augmentera les frais généraux du système.
Ce que nous devons savoir, c'est que lorsqu'une machine virtuelle rencontre une série de demandes et de versions continues du même verrou, elle intégrera toutes les opérations de verrouillage dans une demande au verrou, réduisant ainsi le nombre de demandes de verrouillage. Cette opération est appelée grossièreté de verrouillage. Voici une démonstration de l'exemple d'intégration:
public void syncmehod () {synchronisé (lock) {méthode1 ();} synchronisé (lock) {méthode2 ();}} le formulaire après l'intégration JVM: public void syncMehod () {synchronisé (lock) {méthode1 (); méthode2 ();}}}Par conséquent, une telle intégration donne à nos développeurs un bon effet de démonstration sur la compréhension de la granularité de verrouillage.
L'informatique parallèle sans verrouillage <br /> Ce qui précède a passé beaucoup de temps à parler de verrouillage, et il est également mentionné que le verrouillage apportera des frais généraux de ressources supplémentaires pour une certaine commutation de contexte. Dans une grande concurrence, la concurrence féroce pour le "verrouillage" peut devenir un goulot d'étranglement du système. Par conséquent, une méthode de synchronisation non bloquante peut être utilisée ici. Cette méthode sans verrouillage peut toujours garantir que les données et les programmes maintiennent la cohérence entre plusieurs threads dans un environnement de concurrence élevé.
1. Synchronisation non bloquante / Lockless
La méthode de synchronisation non bloquante se reflète en fait dans le threadlocal précédent. Chaque thread a sa propre copie indépendante des variables, il n'est donc pas nécessaire de s'attendre lors du calcul en parallèle. Ici, l'auteur recommande principalement une méthode de contrôle de concurrence sans serrure plus importante basée sur l'algorithme CAS compare et échange.
Le processus d'algorithme CAS: il contient 3 paramètres CAS (V, E, N). V représente la variable à mettre à jour, E représente la valeur attendue et N représente la nouvelle valeur. La valeur de V sera définie sur n uniquement lorsque la valeur V est égale à la valeur E. Si la valeur V est différente de la valeur E, cela signifie que d'autres threads ont fait des mises à jour et que le thread actuel ne fait rien. Enfin, CAS renvoie la vraie valeur du V. actuel lors du fonctionnement du CAS, il est effectué avec une attitude optimiste, et il croit toujours qu'il peut terminer avec succès l'opération. Lorsque plusieurs threads utilisent CAS pour faire fonctionner une variable en même temps, un seul gagnera et sera mis à jour avec succès, tandis que le reste de Junhui échoue. Le thread défaillant ne sera pas suspendu, il est seulement dit que l'échec est autorisé, et il est autorisé à réessayer, et bien sûr, le thread échoué permettra également d'abandonner l'opération. Sur la base de ce principe, le fonctionnement CAS est opportun sans verrous, et d'autres threads peuvent également détecter des interférences au thread actuel et le gérer de manière appropriée.
2. Fonctionnement du poids atomique
Le package Java.util.concurrent.atomic de JDK fournit des classes d'opération atomique implémentées à l'aide d'algorithmes sans verrouillage, et le code utilise principalement l'implémentation de code natif sous-jacente. Les étudiants intéressés peuvent continuer à suivre le code de niveau natif. Je ne publierai pas l'implémentation du code de surface ici.
Ce qui suit utilise principalement un exemple pour montrer l'écart de performance entre les méthodes de synchronisation ordinaires et la synchronisation sans serrure:
classe publique Testatomic {private static final int max_threads = 3; private static final int tas task_count = 3; private static final int cible_count = 100 * 10000; private atomicInteger compte = new atomicInteger (0); private int count = 0; synchronisé int inc () {return ++ count Runnable {String name; Long starttime; Testatomic out; Public Syncthread (Testatomic O, Long Starttime) {this.out = o; this.starttime = startTime; } @Override public void run () {int v = out.inc (); while (v <cible_count) {v = out.inc (); } Long EndTime = System.CurrentTimeMillis (); System.out.println ("Syncthread Dépens:" + (Endtime - starttime) + "MS" + ", v =" + v); }} classe publique ATOMICTHREAD implémente Runnable {String Name; Long starttime; public atomicthread (long starttime) {this.starttime = startTime; } @Override public void run () {int v = compte.incrementAndget (); while (v <Target_Count) {v = compte.incrementAndget (); } Long EndTime = System.CurrentTimeMillis (); System.out.println ("ATOMICTHREAD SENSE:" + (Endtime - Starttime) + "MS" + ", v =" + V); }} @ TestPublic void testSync () lève InterruptedException {ExecutorService exe = exécuteurs.NewFixeDThreadPool (max_threads); Long startTime = System.CurrentTimemillis (); SynCTHREAD SYNC = NOUVEAU SYNCTHREAD (This, Starmtime); for (int i = 0; i <task_count; i ++) {exe.submit (sync); } Thread.sleep (10000);} @ testpublic void testatomic () lève InterruptedException {exécutorOrvice exe = exécutor.newfixEdThreadpool (max_threads); Long startTime = System.CurrentTimemillis (); Atomicthread atomic = new atomicthread (starttime); for (int i = 0; i <task_count; i ++) {exe.submit (atomic); } Thread.sleep (10000);}} Les résultats des tests sont les suivants:
TestSync ():
SYNCTHREAD COUPS: 201MS, V = 1000002
SYNCTHREAD COUPS: 201MS, V = 1000000
SYNCTHREAD COUPS: 201MS, V = 1000001
Testatomic ():
ATOMICTHREAD SOUPE: 43 ms, v = 1000000
ATOMICTHREAD COUPS: 44 ms, v = 1000001
ATOMICTHREAD COUPS: 46 ms, v = 1000002
Je crois que de tels résultats de test refléteront clairement les différences de performance entre le verrouillage interne et les algorithmes de synchronisation non bloquants. Par conséquent, l'auteur recommande de considérer directement cette classe atomique sous atomique.
Conclusion
Enfin, j'ai trié les choses que je veux exprimer. En fait, il y a encore des classes comme CountdownLatch qui n'ont pas été mentionnées. Cependant, ce qui est mentionné ci-dessus est certainement le cœur de la programmation simultanée. Peut-être que certains lecteurs peuvent voir beaucoup de tels points de connaissance sur Internet, mais je pense toujours que ce n'est que par comparaison que les connaissances peuvent être trouvées dans un scénario d'utilisation approprié. Par conséquent, c'est aussi la raison pour laquelle l'éditeur a compilé cet article, et j'espère que cet article pourra aider davantage d'étudiants.
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.