2010-09-21 7 views
0

J'ai lu plusieurs questions similaires à celle-ci, mais aucune des réponses ne fournit d'idées sur la façon de nettoyer la mémoire tout en conservant l'intégrité du verrou. J'estime que le nombre de paires valeur/clé à un moment donné est de dizaines de milliers, mais le nombre de paires valeur/clé sur la durée de vie de la structure de données est pratiquement infini (en réalité, il ne serait probablement pas plus plus d'un milliard, mais je code dans le pire des cas).verrouillage sur une clé de cache

J'ai une interface:

public interface KeyLock<K extends Comparable<? super K>> { 

    public void lock(K key); 

    public void unock(K key); 

} 

avec une implémentation par défaut:

public class DefaultKeyLock<K extends Comparable<? super K>> implements KeyLock<K> { 

    private final ConcurrentMap<K, Mutex> lockMap; 

    public DefaultKeyLock() { 
    lockMap = new ConcurrentSkipListMap<K, Mutex>(); 
    } 

    @Override 
    public void lock(K key) { 
    Mutex mutex = new Mutex(); 
    Mutex existingMutex = lockMap.putIfAbsent(key, mutex); 
    if (existingMutex != null) { 
     mutex = existingMutex; 
    } 
    mutex.lock(); 
    } 

    @Override 
    public void unock(K key) { 
    Mutex mutex = lockMap.get(key); 
    mutex.unlock(); 
    } 

} 

Cela fonctionne bien, mais la carte ne fait jamais nettoyé. Ce que j'ai à ce jour pour une mise en œuvre est propre:

public class CleanKeyLock<K extends Comparable<? super K>> implements KeyLock<K> { 

    private final ConcurrentMap<K, LockWrapper> lockMap; 

    public CleanKeyLock() { 
    lockMap = new ConcurrentSkipListMap<K, LockWrapper>(); 
    } 

    @Override 
    public void lock(K key) { 
    LockWrapper wrapper = new LockWrapper(key); 
    wrapper.addReference(); 
    LockWrapper existingWrapper = lockMap.putIfAbsent(key, wrapper); 
    if (existingWrapper != null) { 
     wrapper = existingWrapper; 
     wrapper.addReference(); 
    } 

    wrapper.addReference(); 
    wrapper.lock(); 
    } 

    @Override 
    public void unock(K key) { 
    LockWrapper wrapper = lockMap.get(key); 
    if (wrapper != null) { 
     wrapper.unlock(); 
     wrapper.removeReference(); 
    } 
    } 

    private class LockWrapper { 

    private final K key; 

    private final ReentrantLock lock; 

    private int referenceCount; 

    public LockWrapper(K key) { 
     this.key = key; 
     lock = new ReentrantLock(); 
     referenceCount = 0; 
    } 

    public synchronized void addReference() { 
     lockMap.put(key, this); 
     referenceCount++; 
    } 

    public synchronized void removeReference() { 
     referenceCount--; 
     if (referenceCount == 0) { 
     lockMap.remove(key); 
     } 
    } 

    public void lock() { 
     lock.lock(); 
    } 

    public void unlock() { 
     lock.unlock(); 
    } 
    } 

} 

Cela fonctionne pour deux fils l'accès à une seule serrure à clé, mais une fois un troisième fil est introduit l'intégrité de verrouillage n'est plus garantie. Des idées?

+0

Ignorer la AddReference superflu() appelle dans le procédé de verrouillage. J'essayais quelques idées et j'ai oublié de les sortir en postant le code ici. – user453385

Répondre

0

Je n'achète pas que cela fonctionne pour deux threads. Considérez ceci:

  • (Discussion A) appelle verrou (x), détient désormais verrouillage x
  • interrupteur de fil
  • (filetage B) appelle verrouillage (x), retourne putIfAbsent() l'emballage en cours pour x
  • appels fil commutateur
  • (discussion a) déverrouiller (x), le nombre de référence wrapper frappe 0 et il se retire de la carte
  • (discussion a) appelle verrouillage (x), putIfAbsent() insère une nouvelle emballage pour x
  • (Discussion A) verrouille sur le nouveau commutateur de fil wrapper
  • (fil B) verrouille sur l'ancien emballage

Que diriez-vous:

  • LockWrapper commence par un compte de référence de 1
  • addReference() renvoie la valeur false si le compteur de références est 0
  • dans lock(), si existingWrapper! = Null, nous l'appelons addReference(). Si cela renvoie false, il a déjà été retiré de la carte, donc nous rebouclons et essayons de nouveau à partir de la putIfAbsent()
+0

Vous avez raison dans le cas général. J'aime votre idée de re-essayer l'putIfAbsent, d'autant plus que je peux mettre en œuvre que d'une opération de comparaison et-échange. – user453385

0

J'utiliserais un tableau fixe par défaut pour un verrou rayé, puisque vous pouvez le dimensionner au niveau de concurrence que vous attendez. Bien qu'il puisse y avoir des collisions de hachage, un bon épandeur permettra de résoudre ce problème. Si les verrous sont utilisés pour des sections critiques courtes, vous pouvez créer une contention dans ConcurrentHashMap qui annule l'optimisation.

Vous pouvez adapter mon implémentation, même si je n'ai implémenté la version dynamique que pour le plaisir. Cela ne semblait pas utile dans la pratique, donc seul le fixe était utilisé en production. Vous pouvez utiliser la fonction hash() de ConcurrentHashMap pour obtenir une bonne répartition.

ReentrantStripedLock dans, http://code.google.com/p/concurrentlinkedhashmap/wiki/IndexableCache

Questions connexes