блокировка для каждого ключа, отличная от блокировки всей карты в HashMap

У меня есть большая карта = ConcurrentHashMap() в Java, а Key, Value - это какая-то структура объекта. Предположим, что набор ключей этой карты равен keySet.

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

Учитывая, что это большая карта, использование блокировки каждого ключа может быть неприемлемым подходом.

multiThread():
    for(0 to N):
        K = subset(keySet, m) where m is much smaller than keySet.size
        lock(map)
        for(key in K):
            result = func1(map.get(key), result)
        for(key in K):
            map.put(key, func2(map.get(key), result))
        releaseLock(map)

person Will Yang    schedule 12.02.2015    source источник


Ответы (1)


в java 8+ ConcurrentHashMap как compute(), которая позволяет вам выполнять атомарную операцию чтения-изменения-записи для одного ключа, поэтому вы можете сделать что-то вроде:

map.compute(key, () -> {
    //call func2 to compute new value and return it
});

если, однако, вы хотите атомарное чтение-изменение-запись для всего набора ключей (поэтому сначала вы перебираете свой набор ключей для вычисления результата, а затем меняете все эти ключи, используя этот предварительно вычисленный результат), тогда в ConcurrentHashMap нет возможности чтобы обеспечить эту блокировку.

однако вы можете использовать гуавы. Полосатый замок, вот так:

Striped<Lock> arrayOfLocks = Striped.lock(20);
// ...later on...
K = subset(keySet, m);
Iterable<Lock> toObtain = arrayOfLocks.bulkGet(K);
for (Lock l : toObtain) { lock it }
try {
   //do your modifications - your holding the stripe locks for all the keys
} finally {
   for (Lock l : toObtain) { unlock it }
}

чередование блокировок — это концепция назначения блокировок различным «областям» структуры данных — здесь это делается с помощью хэш-кода ваших ключей.

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

Striped позаботится о том, чтобы вернуть замки в том же порядке, чтобы один и тот же набор ключей запирался, чтобы избежать проблемы обедающих философов.

person radai    schedule 12.02.2015
comment
Это означает, что никакой другой ключ put()/get()/compute() не заблокирован? - person JavaTechnical; 20.05.2019