Мне нужно написать несколько специфическую реализацию кеша, который имеет уникальные ключи, но может содержать повторяющиеся значения, например:
"/path/to/one" -> 1
"/path/to/two" -> 2
"/path/to/vienas" -> 1
"/path/to/du" -> 2
Класс должен предлагать неблокирующее чтение / поиск ключей, но также имеет типичный create / обновить / удалить мутаторы. Например, удаление значения 2
должно привести к
"/path/to/one" -> 1
"/path/to/vienas" -> 1
Чтения для этого кэша намного перевешивают записи, поэтому производительность записи не является проблемой - до тех пор, пока параллельные записи не выполняются друг над другом. Общее количество записей, скорее всего, будет меньше 1000, поэтому периодический перебор значений по-прежнему возможен.
Итак, я написал что-то вроде этого (псевдокод):
//
// tl;dr all writes are synchronized on a single lock and each
// resets the reference to the volatile immutable map after finishing
//
class CopyOnWriteCache {
private volatile Map<K, V> readOnlyMap = ImmutableMap.of();
private final Object writeLock = new Object();
public void add(CacheEntry entry) {
synchronized (writeLock) {
readOnlyMap = new ImmutableMap.Builder<K, V>()
.addAll(readOnlyMap)
.add(entry.key, entry.value)
.build();
}
}
public void remove(CacheEntry entry) {
synchronized (writeLock) {
Map<K, V> filtered = Maps.filterValues(readOnlyMap, somePredicate(entry));
readOnlyMap = ImmutableMap.copyOf(filtered);
}
}
public void update(CacheEntry entry) {
synchronized (writeLock) {
Map<K, V> filtered = Maps.filterValues(readOnlyMap, somePredicate(entry));
readOnlyMap = new ImmutableMap.Builder<K, V>()
.addAll(filtered)
.add(entry.key, entry.value)
.build();
}
}
public SomeValue lookup(K key) {
return readOnlyMap.get(key);
}
}
После написания вышеизложенного я понял, что ConcurrentHashMap
также предлагает неблокирующие чтения, которые сделают все мои усилия бессмысленными, но есть утверждение в его Javadoc, который поднимает бровь:
iterators are designed to be used by only one thread at a time
Итак, если я заменю использование volatile ImmutableMap
на final ConcurrentHashMap
и удалю все синхронизированные
блоки, возможно ли это что конкурирующие параллельные мутаторы аннулируют друг друга? Например, я могу представить, как два одновременных вызова remove
приведут к состоянию гонки, полностью аннулируя результаты первого remove
.
Единственное улучшение, которое я вижу, заключается в том, что, используя final ConcurrentHashMap
и , оставляя синхронизированными
как есть, я мог по крайней мере избежать ненужного копирования данных.
Есть ли в этом смысл - или, может быть, я что-то упускаю из виду? Может ли кто-нибудь предложить другие альтернативы этому решению?