2013-04-01 2 views
0

Мне нужно, чтобы несколько потоков не пытались получить доступ к одному и тому же ресурсу одновременно. У меня есть куча этих ресурсов, поэтому я хочу иметь отдельный объект блокировки для каждого ресурса (а не одну глобальную блокировку), чтобы потоки не излишне блокировали друг друга.Синхронизация по идентификатору с удалением

Eddie представляет прекрасное решение для этого использования ConcurrentMap.putIfAbsent() в https://stackoverflow.com/a/659939/82156.

// From https://stackoverflow.com/a/659939/82156 
public Page getPage(Integer id) { 
    Page p = cache.get(id); 
    if (p == null) { 
    synchronized (getCacheSyncObject(id)) { 
     p = getFromDataBase(id); 
     cache.store(p); 
    } 
    } 
} 

private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>(); 

private Object getCacheSyncObject(final Integer id) { 
    locks.putIfAbsent(id, id); 
    return locks.get(id); 
} 

Однако одна проблема с этой реализацией заключается в том, что хешмап будет неограниченно расти. Есть ли способ удалить хеш-ключ, когда поток завершен, не прикручивая параллелизм?

+0

И в чем проблема с 'locks.remove (id, id)'? – OldCurmudgeon

+0

Карта блокировки будет меньше, чем фактический кеш. Есть ли механизм для очистки кеша? Если это так, возможно, необходимо очистить карту блокировки. – flup

ответ

1

Pyrce заставило меня понять, что не требуется, чтобы каждый ресурс имел свой собственный объект блокировки. Скорее, я мог бы использовать пул, скажем, 100 объектов блокировки, которые могут быть разделены между ресурсами. Таким образом, набор объектов блокировки не будет расти неограниченно, но я по-прежнему получаю большинство преимуществ параллелизма, которые я надеялся получить, удалив единую глобальную блокировку.

Это означало, что мне больше не нужно было использовать ConcurrentHashMap, и вместо этого он мог просто использовать простой массив, который был инициативно инициализирован.

// getPage() remains unchanged 
public Page getPage(Integer id) { 
    Page p = cache.get(id); 
    if (p == null) { 
    synchronized (getCacheSyncObject(id)) { 
     p = getFromDataBase(id); 
     cache.store(p); 
    } 
    } 
} 

private int MAX_LOCKS = 100; 
private Object[] locks = new Object[MAX_LOCKS]; 

{ 
    for(int i=0; i<locks.length; ++i) 
     locks[i] = new Object(); 
} 


private Object getCacheSyncObject(final Integer id) { 
    return locks[ id % MAX_LOCKS ]; 
} 
1

Если вы знаете, что конкретный поток является последним потоком для запроса конкретной блокировки, вы можете просто использовать locks.remove(id, id). В противном случае вам понадобится атомное обновление для двух событий, которые не могут быть легко синхронизированы вместе.

Если вы отпустите и затем удалите блокировку, другой поток может захватить блокировку, которую вы выпустили, а дополнительный поток делает новый объект блокировки незаметным для исходного объекта блокировки. В этой ситуации вы можете получить два потока, вызывающих getFromDataBase(id) или cache.store(p) одновременно. Если вы удалите и затем отпустите вместо этого, у вас может быть другой поток, ожидающий, когда вы отпустите старый замок, в то время как новый поток уже сделал новую блокировку. Такое же столкновение может произойти.

По существу, вы не можете атомизировать блокировку и удалить ее из HashMap без добавления новой блокировки в систему. В этом случае вы либо получаете глобальную блокировку, которая побеждает ускорение специфической блокировки хэша, либо вам нужна дополнительная блокировка для каждой страницы, которая сама будет иметь те же проблемы с удалением. В качестве альтернативы, если у вас есть доступ к внутренним компонентам HashMap, вы можете попробовать какую-то сложную блокировку блокировки ведра для своей логики более высокого уровня, хотя я бы не рекомендовал пытаться реализовать такую ​​вещь.

Одним из решений для ограничения размера хэш-карты является использование сопоставления с фиксированным размером. Измените идентификатор Integer с большим числом (10 000) и используйте модифицированный номер как уникальный идентификатор блокировки. Вероятность столкновения двух хешей и требование одинаковой блокировки для разных страниц была бы очень маленькой, и у вас была бы жесткая кепка на памяти, потребляемой замками. В этом случае вам действительно не нужен HashMap, поскольку вы могли предварительно выделить объекты блокировки Integer в статический массив констант и запросить хэш-код мод непосредственно из объектов id.

Также вы должны быть осторожны, чтобы кеш читался за пределами блокировки, если только сам кеш не защищен от одновременной чтения-записи, поскольку поток может писать, в то время как другой считывает с того, как код указан в вопросе.

+0

+1 для предложения об изменении id в фиксированном размере целочисленного списка.Иногда вы можете создавать два разных ресурса на одной и той же блокировке, но это нормально для моих целей. – emmby

Смежные вопросы