У меня есть код, который реализует "обработчик блокировки" для произвольных ключей. Учитывая 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 не приобретают и не освобождают блокировку. Похоже, что достаточно сложной синхронизации, и это приведет к медленному функционированию алгоритма. Возможно, нам нужно время от времени очищать карту, когда размер карты превышает некоторое ограниченное значение.
Я потратил много времени, но, к сожалению, у меня нет идей, как этого добиться.