У меня есть код, который реализует "обработчик блокировки" для произвольных ключей. Учитывая key
, он гарантирует, что только один поток за раз может process
, который (или равен) (что здесь означает вызов externalSystem.process(key)
).
До сих пор у меня такой код:
public class MyHandler {
private final SomeWorkExecutor someWorkExecutor;
private final ConcurrentHashMap<Key, Lock> lockMap = new ConcurrentHashMap<>();
public void handle(Key key) {
// This can lead to OOM as it creates locks without removing them
Lock keyLock = lockMap.computeIfAbsent(
key, (k) -> new ReentrantLock()
);
keyLock.lock();
try {
someWorkExecutor.process(key);
} finally {
keyLock.unlock();
}
}
}
Я понимаю, что этот код может привести к OutOfMemoryError
, потому что нет однозначной карты.
Я думаю о том, как создать карту, которая будет накапливать ограниченное количество элементов. Когда предел будет превышен, мы должны заменить старый элемент доступа новым (этот код должен быть синхронизирован с самым старым элементом в качестве монитора). Но я не знаю, как иметь обратный вызов, который скажет мне, что предел превышен.
Поделитесь своими мыслями.
P.S.
Я перечитываю задачу, и теперь вижу, что у меня есть ограничение, что метод handle
не может быть вызван более чем 8 потоками. Я не знаю, как это может мне помочь, но я только что упомянул об этом.
P.S.2
от @Boris Spider было предложено приятное и простое решение:
} finally {
lockMap.remove(key);
keyLock.unlock();
}
Но после того, как Борис заметил, что код нам не потокобезопасен, потому что он нарушает поведение:
позволяет исследовать 3 потока, вызываемых с одинаковым ключом:
- Thread # 1 получает блокировку и теперь до
map.remove(key);
- Thread # 2 вызывает ключ равенства, чтобы он дождался блокировки потока # 1.
- тогда поток # 1 выполнить
map.remove(key);
. После этого потока # 3 вызывается методhandle
. Он проверяет, что блокировка для этого ключа отсутствует на карте, таким образом, он создает новую блокировку и приобретает ее. - Тема № 1 освобождает замок, и, таким образом, поток # 2 приобретает его.
Таким образом, поток # 2 и поток # 3 можно вызывать параллельно для равных ключей. Но это не должно допускаться.
Чтобы избежать этой ситуации, перед очисткой карты мы должны заблокировать любой поток, чтобы получить блокировку, в то время как все потоки из waitset не приобретают и не освобождают блокировку. Похоже, что достаточно сложной синхронизации, и это приведет к медленному функционированию алгоритма. Возможно, нам нужно время от времени очищать карту, когда размер карты превышает некоторое ограниченное значение.
Я потратил много времени, но, к сожалению, у меня нет идей, как этого добиться.