Maison >Java >javaDidacticiel >Apprendre la concurrence Java : optimisation des verrous, ConcurrentHashMap, séparation des verrous
Quelques suggestions pour aider à améliorer les performances du verrouillage
Réduire le maintien du verrouillage le temps
n'est synchronisé que lorsque cela est nécessaire, ce qui réduit considérablement le temps de maintien du verrou, réduit le risque de conflits de verrouillage et améliore les capacités de concurrence
Par exemple, utilisez le verrouillage de synchronisation , essayez de l'ajouter lorsque l'objet doit partager un état variable, au lieu d'ajouter aveuglément synchroniser avant toute la méthode, verrouillez directement l'objet appelant cette méthode, ce qui augmente la probabilité de concurrence de verrouillage
J'ai parlé de l'utilisation de la séparation en lecture-écriture ReadWriteLock pour améliorer l'efficacité
Une extension est la stratégie de séparation des verrous
Pour séparer les verrous exclusifs A. Le scénario de référence typique pour cette technologie est la file d'attente de tâches LinkedBlockingQueue. Comme nous l'avons dit précédemment, il s'agit d'une file d'attente de tâches illimitée, implémentée sur la base d'une liste chaînée. Ses méthodes take() et put() agissent sur le front-end et la queue de. respectivement dans la file d'attente et sont complémentaires, donc dans l'implémentation de jdk, deux verrous sont fournis pour ces deux opérations,
Par exemple, lorsque plusieurs threads exécutent l'opération put(), ils en ont le plus besoin. en compétition pour putLock, et l'opération take est en compétition pour takeLock
/** Lock held by take, poll, etc */ private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */ private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */ private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition();
Les verrous qui ont continuellement demandé et libéré la même ressource peuvent être intégrés dans un seul demande de temps pour le verrou, réduisant ainsi la consommation des actions d'application et de libération.
Par exemple :
for(int i=0;i<n;i++){ synchronized(lock){} }
est optimisé pour
synchronized(lock){ for(int i=0;i<n;i++){} }
Un scénario d'application typique de cette technologie est ConcurrentHashMap Comparé à HashMap, il est thread-safe et comparé à HashTable, il est très efficace et simultané
Il y a une grande différence entre l'implémentation de ConcurrentHashMap dans jdk1. 7 et jdk1.8
La structure sous-jacente de ConcurrentHashMap est un tableau Segment. La taille par défaut est de 16. Chaque tableau Segment peut être considéré comme un petit HashMap, ce qui signifie que le tableau Segment est implémenté à l'aide d'une liste chaînée. et un tableau.
Par exemple, si nous devons insérer une nouvelle paire clé-valeur dans la carte, nous trouvons d'abord dans quel segment elle doit être insérée en fonction du hashcode de la clé, puis verrouillez ce segment pour terminer l'opération put. Ici, le segment agit comme un verrou, car la classe Segment hérite de la classe ReentrantLock Lors de l'acquisition du verrou, vous n'utilisez pas directement le verrou pour l'acquérir, car cela. La méthode se bloquera si elle ne parvient pas à acquérir le verrou. En fait, il utilise Spin Lock. Si tryLock ne parvient pas à acquérir le verrou, cela signifie que le verrou est occupé par d'autres threads. À ce moment, le verrou est à nouveau appliqué via tryLock via une boucle. Ainsi, en multithreading, tant que les données insérées ne se trouvent pas dans un segment et que la concurrence des verrous ne provoquera pas de blocage, une véritable concurrence peut être obtenue entre les threads.
Problème : lorsque des opérations inter-segments sont nécessaires, c'est-à-dire lorsque le système doit obtenir un verrou global, il est plus gênant de vérifier l'état de verrouillage de chaque segment et d'obtenir uniquement le verrou global. verrous de tous les segments. Pour obtenir des informations globales. Par exemple, la méthode size() de ConcurrentHashMap nécessite le verrouillage de tous les sous-segments
La concurrence maximale du ConcurrentHashMap de jdk1.7 est égale au nombre de segments Afin d'améliorer encore la concurrence, jdk 1.8 a abandonné la solution de verrouillage de segmentation et a directement utilisé un grand tableau. Dans le même temps, afin d'améliorer les performances d'adressage en cas de collision de hachage, Java 8 convertit la liste chaînée (la complexité temporelle d'adressage est O(N)) en un arbre rouge-noir (la complexité temporelle d'adressage est O(N)) lorsque le la longueur de la liste chaînée dépasse un certain seuil (8) O(long(N))). L'index de la Clé dans le tableau est également déterminé en prenant le modulo de la valeur de hachage de la Clé et la longueur du tableau
Pour l'opération put, si l'élément du tableau correspondant à la clé est nulle, puis définissez-la sur la valeur actuelle via une opération CAS. Si l'élément du tableau correspondant à Key (c'est-à-dire la tête de la liste chaînée ou l'élément racine de l'arborescence) n'est pas nul, utilisez le mot-clé synchronisé pour demander un verrou sur l'élément, puis effectuez l'opération. Si l'opération put amène la longueur de la liste chaînée actuelle à dépasser un certain seuil, la liste chaînée est convertie en arborescence, améliorant ainsi l'efficacité de l'adressage.
Pour les opérations de lecture, puisque le tableau est modifié avec le mot-clé volatile, il n'y a pas lieu de s'inquiéter de la visibilité du tableau. En même temps, chaque élément est une instance de Node (chaque élément dans Java 7 est un HashEntry). Sa valeur de clé et sa valeur de hachage sont modifiées par final et ne peuvent pas être modifiées. Il n'est pas nécessaire de se soucier de leur visibilité après modification. Sa valeur et la référence à l'élément suivant sont modifiées par volatile, et la visibilité est également garantie.
Opération de taille : chaque grand tableau maintient un compteur. La méthode put et la méthode Remove maintiennent la taille de la carte via la méthode addCount. La méthode size obtient la taille de la carte conservée par la méthode addCount via sumCount.
Il est important de noter que la raison pour laquelle chaque tableau contient un compteur au lieu d'utiliser un compteur global dans ConcurrentHashMap est de prendre en compte la concurrence de ConcurrentHashMap : Parce que c'est à ce moment-là que vous devez mettre à jour le compteur, vous n'avez pas besoin de verrouiller l'intégralité de ConcurrentHashMapIl est important de noter que le décompte est volatile, ce qui rend toute mise à jour du décompte immédiatement visible par les autres fils de discussion
Articles connexes :
Framework Java Bootstrap, jQuery, SpringMVC, Hibernate hautes performances et haute concurrence
Solution aux problèmes de haute concurrence dans le système Java
Vidéos associées :
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!