2015-01-19 1 views
10

Я использую WeakHashMap одновременно. Я хочу добиться мелкозернистой блокировки на основе параметра Integer; если поток A должен изменить ресурс, идентифицированный Integer a, и поток B делает то же самое для ресурса, идентифицированного Integer b, тогда их не нужно синхронизировать. Однако, если есть два потока, использующих один и тот же ресурс, скажем, что поток C также использует ресурс, идентифицированный Integer a, тогда, конечно, потоки A и C необходимо синхронизировать с одним и тем же Блоком.Итерация WeakHashMap

Если нет потоков, которым нужен ресурс с идентификатором X, тогда блокировка на карте для ключа = X может быть удалена. Однако в этот момент может появиться другой поток и попытаться использовать блокировку в Map для ID = X, поэтому нам нужно глобальную синхронизацию при добавлении/удалении блокировки. (Это будет единственное место, где каждая нить должна синхронизироваться независимо от параметра Integer). Но нить не может знать, когда удалить блокировку, потому что она не знает, что это последний поток, использующий блокировку.

Вот почему я использую WeakHashMap: когда идентификатор больше не используется, пару ключ-значение можно удалить, когда GC хочет его.

Чтобы убедиться, что у меня есть сильная ссылка на ключ уже существующей записи, и именно эту ссылку на объект, который формирует ключ отображения, мне нужно итерируем Keyset карты:

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 
} 

Теперь, может ли содержимое карты меняться при ее повторении? Я думаю, что нет, потому что, позвонив mrLocks.keySet(), я создал сильную ссылку на все ключи для области итерации. Это верно?

+0

См. Http://stackoverflow.com/questions/2861410/weakhashmap-iteration-and-garbage-collection – ikettu

+1

Я думаю, что нет, из [JavaDoc] (http://docs.oracle.com/javase/7/ docs/api/java/util/WeakHashMap.html # keySet% 28% 29): * "** Набор поддерживается картой, поэтому изменения в карте отражаются в наборе и наоборот. **" * – m0skit0

+0

@ m0skit0 Ах, возможно, вы правы. Возвращенный Set также содержит WeakReference, но он скрыт, как и WeakHashMap. Поэтому я должен сначала взять клон keySet, а затем перебрать клон, я думаю, чтобы убедиться, что я повторяю коллекцию с сильными ссылками. – Timmos

ответ

3

Поскольку API не делает никаких утверждений о Keyset(), я бы рекомендовал использование кэша вроде этого:

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

Таким образом, вы держите ссылку на монитор (сам ключ) в то время как замок на он и позволяет GC завершить его, когда он больше не используется.

Edit:
После обсуждения полезной нагрузки в комментариях, я думал о решении двух кэшей:

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

Пока сильной ссылкой на полезную нагрузку (замок) существует сильная ссылка к отображаемому целому числу в keyCache избегает удаления полезной нагрузки из кеша lockCache.

+0

Почему вы используете 'Collections.synchronizedMap'? Есть уже внешняя синхронизация с использованием ключевого слова 'synchronized', поэтому вам не нужна внутренняя синхронизация? – Timmos

+0

@Timmos copy & pase :-) Вы правы, это в основном бесполезно и может быть удалено. – flo

+1

Я проверил ваш код, и это кажется достаточно чистым, я уже думал о таком решении. Дело в том, что полезная нагрузка (реальный объект блокировки) должна быть включена в значение, что создает потребность в новом классе, который инкапсулирует как слабое ссылочное целое число, так и сильный ссылочный объект Lock, который имел в виду то, что я пытался избежать. Вместо того, чтобы итерации ключей искать правильный экземпляр Integer, вы просто храните его в значении. Ваше решение также кажется правильным. – Timmos