6. Qu’est-ce que Kankarensi ?
La concurrence est une bibliothèque de classes en Java qui contient des classes spéciales optimisées pour travailler sur plusieurs threads. Ces cours sont regroupés dans un packagejava.util.concurrent
. Elles peuvent être schématiquement divisées en fonction des fonctionnalités comme suit : Collections simultanées - un ensemble de collections qui fonctionnent plus efficacement dans un environnement multithread que les collections universelles standard du java.util
package. Au lieu d'un wrapper de base Collections.synchronizedList
bloquant l'accès à l'ensemble de la collection, des verrous sur des segments de données sont utilisés ou le travail est optimisé pour la lecture parallèle des données à l'aide d'algorithmes sans attente. Files d'attente - files d'attente non bloquantes et bloquantes avec prise en charge multi-thread. Les files d'attente non bloquantes sont conçues pour la vitesse et le fonctionnement sans bloquer les threads. Les files d'attente de blocage sont utilisées lorsque vous devez « ralentir » les threads « Producteur » ou « Consommateur » si certaines conditions ne sont pas remplies, par exemple, la file d'attente est vide ou débordée, ou il n'y a pas de « Consommateur » libre. Les synchroniseurs sont des utilitaires auxiliaires pour synchroniser les threads. Ils constituent une arme puissante dans l’informatique « parallèle ». Exécuteurs - contient d'excellents frameworks pour créer des pools de threads, planifier des tâches asynchrones et obtenir des résultats. Locks - représente des mécanismes de synchronisation de threads alternatifs et plus flexibles par rapport aux mécanismes synchronized
de wait
base notify
. Atomique - classes prenant en charge les opérations atomiques sur les primitives et les références. Source:notifyAll
7. Quels cours de Kankarensi connaissez-vous ?
La réponse à cette question est parfaitement exposée dans cet article . Je ne vois pas l’intérêt de tout réimprimer ici, je ne donnerai donc des descriptions que des cours avec lesquels j’ai eu l’honneur de me familiariser brièvement. ConcurrentHashMap<K, V> - ContrairementHashtable
aux blocs et synhronized
sur HashMap
, les données sont présentées sous forme de segments, divisés en hachages de clés. En conséquence, les données sont accessibles par segments plutôt que par objet unique. De plus, les itérateurs représentent les données pour une période de temps spécifique et ne lancent pas de fichiers ConcurrentModificationException
. AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray - Et si dans une classe vous deviez synchroniser l'accès à une variable simple de typeint
? Vous pouvez utiliser des constructions avec synchronized
, et lors de l'utilisation d'opérations atomiques set/get
, volatile
. Mais vous pouvez faire encore mieux en utilisant de nouvelles classes Atomic*
. Grâce à l'utilisation de CAS, les opérations avec ces classes sont plus rapides que si elles étaient synchronisées via synchronized/volatile
. De plus, il existe des méthodes d'addition atomique d'une quantité donnée, ainsi que d'incrémentation/décrémentation.
8. Comment fonctionne la classe ConcurrentHashMap ?
Au moment de son introduction,ConcurrentHashMap
les développeurs Java avaient besoin de l'implémentation de hash map suivante :
- Sécurité du fil
- Aucun verrou sur toute la table lors de l'accès
- Il est souhaitable qu'il n'y ait pas de verrous de table lors de l'exécution d'une opération de lecture
ConcurrentHashMap
sont les suivantes :
-
Éléments de la carte
Contrairement aux éléments
HashMap
,Entry
inConcurrentHashMap
sont déclarés commevolatile
. Il s'agit d'une fonctionnalité importante, également en raison des modifications apportées à JMM .static final class HashEntry<K, V> { final K key; final int hash; volatile V value; final HashEntry<K, V> next; HashEntry(K key, int hash, HashEntry<K, V> next, V value) { this .key = key; this .hash = hash; this .next = next; this .value = value; } @SuppressWarnings("unchecked") static final <K, V> HashEntry<K, V>[] newArray(int i) { return new HashEntry[i]; } }
-
Fonction de hachage
ConcurrentHashMap
une fonction de hachage améliorée est également utilisée.Laissez-moi vous rappeler à quoi cela ressemblait dans
HashMap
le JDK 1.2 :static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
Version de ConcurrentHashMap JDK 1.5 :
private static int hash(int h) { h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }
Pourquoi est-il nécessaire de rendre la fonction de hachage plus complexe ? Les tables d'une carte de hachage ont une longueur déterminée par une puissance de deux. Pour les codes de hachage dont les représentations binaires ne diffèrent pas en position basse et haute, nous aurons des collisions. Augmenter la complexité de la fonction de hachage résout simplement ce problème, réduisant ainsi le risque de collisions sur la carte.
-
Segments
La carte est divisée en N segments différents (16 par défaut, la valeur maximale peut être de 16 bits et est une puissance de deux). Chaque segment est une table thread-safe d’éléments de carte. L'augmentation du nombre de segments encouragera les opérations de modification à s'étendre sur plusieurs segments, réduisant ainsi le risque de blocage au moment de l'exécution.
-
Niveau de concurrence
Ce paramètre affecte l'utilisation de la carte mémoire et le nombre de segments dans la carte.
Le nombre de segments sera choisi comme la puissance de deux la plus proche supérieure à ConcurrencyLevel. En réduisant le niveau de concurrence, il est plus probable que les threads bloquent les segments de carte lors de l'écriture. La surestimation de l'indicateur conduit à une utilisation inefficace de la mémoire. Si un seul thread modifie la carte et que les autres lisent, il est recommandé d'utiliser la valeur 1.
-
Total
Ainsi, les principaux avantages et fonctionnalités de mise en œuvre
ConcurrentHashMap
:hashmap
La carte possède une interface d'interaction similaire à- Les opérations de lecture ne nécessitent pas de verrous et sont effectuées en parallèle
- Les opérations d'écriture peuvent souvent également être effectuées en parallèle sans bloquer
- Lors de la création, celui recherché est indiqué
concurrencyLevel
, déterminé par les statistiques de lecture et d'écriture - Les éléments de la carte ont une valeur
value
déclarée commevolatile
9. Qu'est-ce que la classe Lock ?
Pour contrôler l'accès à une ressource partagée, nous pouvons utiliser des verrous comme alternative à l'opérateur synchronisé. La fonctionnalité de verrouillage est fournie dansjava.util.concurrent.locks
. Tout d’abord, le thread tente d’accéder à la ressource partagée. S'il est libre, un verrou est placé sur le fil. Une fois le travail terminé, le verrou sur la ressource partagée est libéré. Si la ressource n'est pas libre et qu'un verrou est déjà placé dessus, alors le thread attend que ce verrou soit libéré. Les classes de verrouillage implémentent une interface Lock
qui définit les méthodes suivantes :
void lock():
attend que le verrou soit acquisboolean tryLock():
essaie d'acquérir un verrou ; si le verrou est obtenu, il renvoie true . Si le verrou n'est pas acquis, il renvoie false . Contrairement à la méthode,lock()
elle n'attend pas pour acquérir un verrou si celui-ci n'est pas disponiblevoid unlock():
enlève le verrouCondition newCondition():
renvoie l'objetCondition
associé au verrou actuel
lock()
, et après avoir fini de travailler avec les ressources partagées, la méthode est appelée unlock()
, qui libère le verrou. L'objet Condition
permet de gérer le blocage. En règle générale, pour travailler avec les verrous, une classe ReentrantLock
du package est utilisée. java.util.concurrent.locks.
Cette classe implémente l'interface Lock
. Examinons l'utilisation de l'API Java Lock en utilisant un petit programme comme exemple : disons que nous avons une classe Resource
avec quelques méthodes thread-safe et des méthodes où la sécurité des threads n'est pas requise.
public class Resource {
public void doSomething(){
// пусть здесь происходит работа с базой данных
}
public void doLogging(){
// потокобезопасность для логгирования нам не требуется
}
}
Prenons maintenant une classe qui implémente l'interface Runnable
et utilise les méthodes de classe Resource
.
public class SynchronizedLockExample implements Runnable{
// экземпляр класса Resource для работы с методами
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
Réécrivons maintenant le programme ci-dessus en utilisant l'API Lock au lieu du synchronized
.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
// лочим на 10 секунд
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//убираем лок
lock.unlock();
}
// Для логгирования не требуется потокобезопасность
resource.doLogging();
}
}
Comme vous pouvez le voir sur le programme, nous utilisons la méthode tryLock()
pour nous assurer que le thread n'attend qu'un certain temps. S'il n'obtient pas de verrou sur l'objet, il se connecte simplement et quitte. Autre point important. Vous devez utiliser un bloc try-finally
pour garantir que le verrou sera libéré même si la méthode doSomething()
lève une exception. Sources:
11. Qu'est-ce qu'un mutex ?
Un mutex est un objet spécial pour synchroniser les threads/processus. Cela peut prendre deux états : occupé et libre. Pour simplifier, un mutex est une variable booléenne qui prend deux valeurs : occupé (vrai) et libre (faux). Lorsqu'un thread souhaite la propriété exclusive d'un objet, il marque son mutex comme étant occupé, et lorsqu'il a fini de travailler avec lui, il marque son mutex comme libre. Un mutex est attaché à chaque objet en Java. Seule la machine Java a un accès direct au mutex. Il est caché au programmeur.12. Qu'est-ce qu'un moniteur ?
Un moniteur est un mécanisme spécial (un morceau de code) - un module complémentaire sur le mutex, qui garantit son bon fonctionnement. Après tout, il ne suffit pas de marquer que l'objet est occupé ; il faut également s'assurer que d'autres threads n'essaient pas d'utiliser l'objet occupé. En Java, le moniteur est implémenté à l'aide du mot-clésynchronized
. Lorsque nous écrivons un bloc synchronisé, le compilateur Java le remplace par trois morceaux de code :
- Au début du bloc
synchronized
, du code est ajouté qui marque le mutex comme étant occupé. - À la fin du bloc,
synchronized
un code est ajouté qui marque le mutex comme libre. - Avant le bloc,
synchronized
du code est ajouté qui vérifie si le mutex est occupé, le thread doit alors attendre qu'il soit libéré.
GO TO FULL VERSION