2015-01-19 10 views
10

sto usando un WeakHashMap contemporaneamente. Voglio ottenere un blocco a grana fine basato su un parametro Integer; se filo A deve modificare una risorsa identificata da integer a e filo B fa lo stesso per risorsa identificata da intero b, allora non devono essere sincronizzati. Tuttavia, se ci sono due thread che utilizzano la stessa risorsa, ad esempio il thread C utilizza anche una risorsa identificata da Integer a, quindi ovviamente i thread A e C devono essere sincronizzati sullo stesso blocco.Iterazione un WeakHashMap

Quando non ci sono più le discussioni che hanno bisogno la risorsa con ID X poi il blocco nella mappa per key = X possono essere rimossi. Tuttavia, un altro thread può entrare in quel momento e provare a utilizzare il blocco in Map per ID = X, quindi abbiamo bisogno di sincronizzazione globale quando si aggiunge/rimuove il blocco. (Questo sarebbe l'unico posto in cui ogni thread deve essere sincronizzato, indipendentemente dal parametro Integer) Ma, un thread non può sapere quando rimuovere il blocco, perché non sa che è l'ultimo thread che usa il lock.

Ecco perché sto utilizzando un WeakHashMap: quando l'ID non è più utilizzato, la coppia chiave-valore può essere rimossa quando il GC vuole.

Per assicurarsi che ho un forte riferimento alla chiave di una voce già esistente, e proprio questo riferimento a un oggetto che costituisce la chiave della mappatura, ho bisogno di iterare l'keySet della mappa:

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 
} 

Ora, il contenuto della mappa può cambiare mentre lo itera? Penso di no, perché chiamando mrLocks.keySet(), ho creato un riferimento forte a tutte le chiavi per l'ambito dell'iterazione. È corretto?

+0

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

+1

Penso di no, da [JavaDoc] (http://docs.oracle.com/javase/7/ docs/api/java/util/WeakHashMap.html # keySet% 28% 29): "** il set è sostenuta dalla mappa, quindi modifiche alla mappa si riflettono nel set, e viceversa **" * * – m0skit0

+0

@ m0skit0 Ah, potresti avere ragione. Il Set restituito conterrà anche WeakReference ma questo è nascosto proprio come WeakHashMap lo nasconde. Quindi dovrei prima prendere un clone del set di chiavi e poi iterare il clone, suppongo, per assicurarmi di iterare una collezione con riferimenti forti. – Timmos

risposta

3

Come l'API non fa affermazioni circa la keySet(), consiglio un utilizzo della cache in questo modo:

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; 
} 

In questo modo si sta tenendo un riferimento al monitor (la chiave stessa), mentre il blocco su e consente al GC di finalizzarlo quando non lo usa più.

Edit:
Dopo la discussione su payload nei commenti ho pensato a una soluzione con due cache:

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; 
} 

Finché un riferimento forte al carico utile (il blocco) esiste, il forte richiamo al numero intero mappato in keyCache evita la rimozione del carico utile dalla cache lockCache.

+0

Perché stai usando 'Collections.synchronizedMap'? C'è già la sincronizzazione esterna usando la parola chiave 'synchronized', quindi non hai bisogno della sincronizzazione interna? – Timmos

+0

@Timmos copy & pase :-) Hai ragione, è praticamente inutile e potrebbe essere rimosso. – flo

+1

Ho ispezionato il tuo codice e questo sembra abbastanza pulito, ho già pensato a una soluzione come questa. Il fatto è che il carico utile (un vero oggetto Lock) deve essere incluso nel valore, il che crea la necessità di una nuova classe che incapsula sia un intero debolmente referenziato sia un forte oggetto referenziato Lock, che è stato infatti quello che ho provato a evitare. Invece di iterare le chiavi per cercare l'istanza Integer corretta, basta tenerla memorizzata nel valore. Anche la tua soluzione sembra un approccio valido. – Timmos