2015-01-19 7 views
10

Ich verwende eine WeakHashMap gleichzeitig. Ich möchte ein feinkörniges Locking basierend auf einem Integer-Parameter erreichen. Wenn Thread A eine Ressource ändern muss, die von Integer a identifiziert wird, und Thread B dasselbe für Ressource ausführt, die von Integer b angegeben wird, müssen sie nicht synchronisiert werden. Wenn es jedoch zwei Threads gibt, die die gleiche Ressource verwenden, sagt Thread C auch eine Ressource, die durch Integer a identifiziert wird, dann müssen Thread A und C natürlich auf derselben Sperre synchronisieren.Iterieren einer WeakHashMap

Wenn es keine weiteren Threads gibt, die die Ressource mit ID X benötigen, kann die Sperre in der Map für Schlüssel = X entfernt werden. Es kann jedoch ein anderer Thread in diesem Moment kommen und versuchen, die Sperre in der Map für ID = X zu verwenden, sodass wir beim Hinzufügen/Entfernen der Sperre eine globale Synchronisation benötigen. (Dies ist die einzige Stelle, an der jeder Thread unabhängig vom Integer-Parameter synchronisiert werden muss.) Ein Thread kann jedoch nicht wissen, wann er die Sperre entfernen muss, weil er nicht weiß, dass es der letzte Thread ist, der die Sperre verwendet.

Deshalb verwende ich eine WeakHashMap: Wenn die ID nicht mehr verwendet wird, kann das Schlüssel-Wert-Paar entfernt werden, wenn der GC es will.

Um sicherzustellen, dass ich einen starken Bezug auf den Schlüssel eines bereits vorhandenen Eintrag haben, und genau das Objektreferenz, die den Schlüssel der Abbildung bildet, muss ich die keySet der Karte iterieren:

synchronized (mrLocks){ 
    // ... do other stuff 
    for (Integer entryKey : mrLocks.keySet()) { 
     if (entryKey.equals(id)) { 
      key = entryKey; 
      break; 
     } 
    } 
    // if key==null, no thread has a strong reference to the Integer 
    // key, so no thread is doing work on resource with id, so we can 
    // add a mapping (new Integer(id) => new ReentrantLock()) here as 
    // we are in a synchronized block. We must keep a strong reference 
    // to the newly created Integer, because otherwise the id-lock mapping 
    // may already have been removed by the time we start using it, and 
    // then other threads will not use the same Lock object for this 
    // resource 
} 

Kann sich der Inhalt der Map während der Iteration ändern? Ich denke nicht, denn mit dem Aufruf mrLocks.keySet() habe ich einen starken Verweis auf alle Schlüssel für den Umfang der Iteration erstellt. Ist das korrekt?

+0

Siehe: http://stackoverflow.com/questions/2861410/weakhashmap-iteration-and-garbage-collection – ikettu

+1

Ich denke nicht, von [JavaDoc] (http://docs.oracle.com/javase/7/ docs/api/java/util/WeakHashMap.html # keySet% 28% 29): * "** Das Set wird von der Map unterstützt, sodass Änderungen an der Map im Set widergespiegelt werden und umgekehrt. **" * – m0skit0

+0

@ m0skit0 Ah, du magst Recht haben. Das zurückgegebene Set enthält auch WeakReference, aber das ist versteckt, genau wie WeakHashMap es versteckt. Daher sollte ich zuerst einen Klon des Schlüsselsatzes nehmen und dann den Klon iterieren, um sicherzustellen, dass ich eine Sammlung mit starken Referenzen wiederhole. – Timmos

Antwort

3

Da die API keine Aussagen über die keySet macht(), würde ich einen Cache-Nutzung wie folgt empfehlen:

private static Map<Integer, Reference<Integer>> lockCache = Collections.synchronizedMap(new WeakHashMap<>()); 

public static Object getLock(Integer i) 
{ 
    Integer monitor = null; 
    synchronized(lockCache) { 
     Reference<Integer> old = lockCache.get(i); 
     if (old != null) 
      monitor = old.get(); 

     // if no monitor exists yet 
     if (monitor == null) { 
      /* clone i for avoiding strong references 
       to the map's key besides the Object returend 
       by this method. 
      */ 
      monitor = new Integer(i); 
      lockCache.remove(monitor); //just to be sure 
      lockCache.put(monitor, new WeakReference<>(monitor)); 
     } 

    } 

    return monitor; 
} 

diese Weise können Sie einen Verweis auf den Monitor halten (der Schlüssel selbst), während Verriegelung auf und erlauben dem GC, es zu finalisieren, wenn es nicht mehr verwendet wird.

Edit:
Nach der Diskussion über Nutzlast in den Kommentaren ich mit zwei Caches über eine Lösung gedacht:

private static Map<Integer, Reference<ReentrantLock>> lockCache = new WeakHashMap<>(); 
private static Map<ReentrantLock, Integer> keyCache = new WeakHashMap<>(); 

public static ReentrantLock getLock(Integer i) 
{ 
    ReentrantLock lock = null; 
    synchronized(lockCache) { 
     Reference<ReentrantLock> old = lockCache.get(i); 
     if (old != null) 
      lock = old.get(); 

     // if no lock exists or got cleared from keyCache already but not from lockCache yet 
     if (lock == null || !keyCache.containsKey(lock)) { 
      /* clone i for avoiding strong references 
       to the map's key besides the Object returend 
       by this method. 
      */ 
      Integer cacheKey = new Integer(i); 
      lock = new ReentrantLock(); 
      lockCache.remove(cacheKey); // just to be sure 
      lockCache.put(cacheKey, new WeakReference<>(lock)); 
      keyCache.put(lock, cacheKey); 
     }     
    } 

    return lock; 
} 

Solange ein starker Hinweis auf die Nutzlast (das Schloss) vorhanden ist, die starke Referenz zu der abgebildeten Ganzzahl in keyCache vermeidet die Entfernung der Nutzlast aus dem Cache lockCache.

+0

Warum verwenden Sie 'Collections.synchronizedMap'? Es gibt bereits eine externe Synchronisation mit dem Schlüsselwort 'synchronized', sodass Sie die interne Synchronisation nicht benötigen. – Timmos

+0

@Timmos kopieren & pase :-) Du hast recht, es ist grundsätzlich nutzlos und könnte entfernt werden. – flo

+1

Ich habe Ihren Code inspiziert und das scheint sauber genug zu sein, ich dachte schon über eine Lösung wie diese nach. Die Sache ist jetzt, dass die Nutzlast (ein echtes Lock-Objekt) in dem Wert enthalten sein muss, was die Notwendigkeit für eine neue Klasse schafft, die sowohl eine schwach referenzierte Ganzzahl als auch ein stark referenziertes Lock-Objekt kapselt, was tatsächlich das war, was ich versuchte vermeiden. Anstatt die Schlüssel zu wiederholen, um nach der richtigen Integer-Instanz zu suchen, behalten Sie sie einfach im Wert. Ihre Lösung scheint auch ein gültiger Ansatz zu sein. – Timmos