Итерирование карты WeakHashMap

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

Если больше нет потоков, которым нужен ресурс с идентификатором 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(), я создал сильную ссылку на все ключи для области итерации. Это правильно?

Ответ 1

Поскольку 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 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 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.