J'ai besoin d'une structure de données LinkedHashMap et thread-safe.
Comment puis je faire ça ?
Vous pouvez envelopper la carte dans Collections.synchronizedMap pour obtenir une hashmap synchronisée qui conserve l'ordre d'insertion. Ce n'est pas aussi efficace qu'un ConcurrentHashMap (et n'implémente pas les méthodes d'interface supplémentaires de ConcurrentMap), mais vous obtenez le comportement (quelque peu) thread-safe.
Même le puissant Google Collections ne semble pas encore avoir résolu ce problème particulier. Cependant, il y a un projet qui essaie de résoudre le problème.
Je dis un peu sur la synchronisation, parce que l'itération n'est toujours pas thread-safe dans le sens où des exceptions de modification simultanées peuvent se produire.
Il existe différentes approches à ce problème. Vous pouvez utiliser:
Collections.synchronizedMap(new LinkedHashMap());
comme d'autres réponses l'ont suggéré, mais il y a plusieurs pièges dont vous devez être au courant. En particulier, vous aurez souvent besoin de conserver le verrou synchronisé des collections pour effectuer une itération sur la collection, ce qui empêche les autres threads d'accéder à la collection tant que vous n'avez pas terminé. ( Voir Théorie et pratique de Java: Classes de collections simultanées ). Par exemple:
synchronized(map) {
for (Object obj: map) {
// Do work here
}
}
En utilisant
new ConcurrentHashMap();
est probablement un meilleur choix car vous n’aurez pas besoin de verrouiller la collection pour la parcourir.
Enfin, vous pouvez envisager une approche de programmation plus fonctionnelle. C'est-à-dire que vous pourriez considérer la carte comme essentiellement immuable. Au lieu d’ajouter à une carte existante, créez-en une nouvelle contenant le contenu de l’ancienne carte plus le nouvel ajout. Cela semble assez bizarre au début, mais c’est en fait la façon dont Scala traite de la simultanéité et des collections
Il y a une implémentation disponible sous Google code. Une citation de leur site:
Une version hautes performances de Java.util.LinkedHashMap à utiliser en tant que cache logiciel.
Conception
- Une liste liée simultanée traverse un ConcurrentHashMap pour fournir un ordre d'éviction.
- Prend en charge les règles d’insertion ordonnée d’insertion et d’accès (FIFO, LRU et seconde chance).
Vous pouvez utiliser un ConcurrentSkipListMap, uniquement disponible dans Java SE/EE 6 ou version ultérieure. C’est dans le respect de l’ordre que les clés sont triées selon leur ordre naturel. Vous devez avoir un comparateur ou créer les clés Objets comparables. Afin de simuler un comportement de carte de hachage liée (l'ordre des itérations est l'ordre chronologique dans lequel les entrées ont été ajoutées), j'ai implémenté mes objets clés à toujours comparer comme étant supérieure à un autre objet donné, à moins qu'il ne soit égal (que ce soit pour l'objet ) . Une carte de hachage liée synchronisée encapsulée ne suffisait pas car, comme indiqué dans http://www.ibm.com/developerworks/Java/library/j-jtp07233.html : "La synchronisation Les wrappers de collections, synchronizedMap et synchronizedList, sont parfois appelés thread-safe conditionnellement - toutes les opérations individuelles sont thread-safe, mais les séquences d'opérations dans lesquelles le flux de contrôle dépend des résultats des opérations précédentes peuvent être soumises à des courses de données. Le Listing 1 montre l’idiome put-if-absent commun - si une entrée n’existe pas déjà dans la mappe, ajoutez-la, mais comme il est écrit, il est possible pour un autre thread d’insérer une valeur avec la même clé entre le moment où la La méthode containsKey () retourne et l'heure à laquelle put méthode est appelée. Si vous voulez assurer une insertion exacte une fois, vous devez encapsuler la paire d'instructions avec un bloc synchronisé qui se synchronise sur la carte m. "
Donc ce qui ne fait qu'aider est un ConcurrentSkipListMap qui est 3 à 5 fois plus lent qu'un ConcurrentHashMap normal.
Comme ConcurrentHashMap offre quelques méthodes supplémentaires importantes qui ne figurent pas dans l'interface Map, emballer simplement un LinkedHashMap avec un synchronizedMap ne vous donnera pas les mêmes fonctionnalités, en particulier, elles ne vous donneront rien comme putIfAbsent (), remplacez (key, oldValue, newValue) et remove (clé, oldValue) qui rendent le ConcurrentHashMap si utile.
À moins qu'une bibliothèque Apache n'implémente ce que vous voulez, vous devrez probablement utiliser une carte LinkedHashMap et fournir vos propres blocs synchronisés {}.
Je viens d'essayer de synchroniser la mappe LRU bornée en fonction de l'ordre d'insertion LinkedConcurrentHashMap ; with Read/Write Lock pour la synchronisation . Donc, lorsque vous utilisez iterator; vous devez acquérir WriteLock pour éviter la ConcurrentModificationException.
C'est mieux que Collections.synchronizedMap.
public class LinkedConcurrentHashMap<K, V> {
private LinkedHashMap<K, V> linkedHashMap = null;
private final int cacheSize;
private ReadWriteLock readWriteLock = null;
public LinkedConcurrentHashMap(LinkedHashMap<K, V> psCacheMap, int size) {
this.linkedHashMap = psCacheMap;
cacheSize = size;
readWriteLock=new ReentrantReadWriteLock();
}
public void put(K key, V value) throws SQLException{
Lock writeLock=readWriteLock.writeLock();
try{
writeLock.lock();
if(linkedHashMap.size() >= cacheSize && cacheSize > 0){
K oldAgedKey = linkedHashMap.keySet().iterator().next();
remove(oldAgedKey);
}
linkedHashMap.put(key, value);
}finally{
writeLock.unlock();
}
}
public V get(K key){
Lock readLock=readWriteLock.readLock();
try{
readLock.lock();
return linkedHashMap.get(key);
}finally{
readLock.unlock();
}
}
public boolean containsKey(K key){
Lock readLock=readWriteLock.readLock();
try{
readLock.lock();
return linkedHashMap.containsKey(key);
}finally{
readLock.unlock();
}
}
public V remove(K key){
Lock writeLock=readWriteLock.writeLock();
try{
writeLock.lock();
return linkedHashMap.remove(key);
}finally{
writeLock.unlock();
}
}
public ReadWriteLock getLock(){
return readWriteLock;
}
public Set<Map.Entry<K, V>> entrySet(){
return linkedHashMap.entrySet();
}
}
La réponse est à peu près non, rien n’est équivalent à un ConcurrentHashMap qui est trié (comme le LinkedHashMap). Comme d'autres personnes l'ont souligné, vous pouvez envelopper votre collection à l'aide de Collections.synchronizedMap (-yourmap-), mais cela ne vous donnera pas le même niveau de verrouillage à grain fin. Cela bloquera simplement la carte entière à chaque opération.
Votre meilleur choix est soit d’utiliser synchronisé tout accès à la carte (là où ça compte, bien sûr. Vous pouvez ne pas vous soucier des lectures sales, par exemple), ou d’écrire un wrapper autour de la carte qui détermine le moment où elle doit ou non se verrouiller .