для кода ниже:
public ReentrantReadWriteLock getLock(String tableName) {
ReentrantReadWriteLock lock = locksMap.get(tableName);
if (lock == null) {
lock = new ReentrantReadWriteLock();
locksMap.put(tableName, lock);
}
}
//где locksMap - это HashMap с ключом String (tableName) и значением ReentrantReadWriteLock (Lock).
Мой вопрос в том, что если потоки одновременно обращаются к этому методу, они получат разные объекты Lock с тем же "именем таблицы", потому что методы get и put Map вызываются отдельно.
Любые решения с объяснением будут оценены? Заранее спасибо.
Использование ConcurrentMap
обычно обеспечивает лучшую производительность, чем synchronized
блок.
ConcurrentMap<String, ReadWriteLock> lockMap = new ConcurrentHashMap<>();
ReadWriteLock getLock(String key) {
ReadWriteLock lock = lockMap.get(key);
if (lock == null) {
lock = new ReentrantReadWriteLock();
ReadWriteLock other = lockMap.putIfAbsent(key, lock);
if (other != null) {
// another thread putIfAbsent won
lock = other;
}
}
return lock;
}
ConcurrentMap<String, ReadWriteLock> lockMap = new ConcurrentHashMap<>();
ReadWriteLock getLock(String key) {
return lockMap.computeIfAbsent(key, ReentrantReadWriteLock::new);
}
Во-первых, такая реализация, как ConcurrentHashMap
, документирована, чтобы не использовать блокировки при чтении. Таким образом, в этом случае, когда кажется, что вы намерены получить блокировку для одного ключа гораздо больше, чем вы собираетесь создавать новые блокировки, это уменьшит конфликт потоков. Если вы использовали synchronized
, даже если блокировка уже была создана, вы заставляете каждый поток проходить один файл через критический раздел.
Кроме того, реализации могут выполнять более сложные формы блокировки или даже блокировки штрихов, так что два сценария не обязательно блокируют друг друга (если они записываются в разные разделы базовой структуры данных). Опять же, synchronized
использует один объект монитора и не может получить информацию о деталях базовой структуры данных.
Версия Java 8 становится одним лайнером благодаря lambdas и ссылки на функции. Синтаксис ::new
относится к публичному конструктору no-arg прилегающего класса ReentrantReadWriteLock
. Метод computeIfAbsent
будет вызывать только этот конструктор, если это необходимо, и в основном делает всю работу шаблона в версии Java 7 выше для вас. Это особенно полезно, если затраты на создание нового объекта дороги или имеют неблагоприятные побочные эффекты. Обратите внимание, что версия Java 7 должна создать новый экземпляр блокировки при определенных обстоятельствах и что новый объект никогда не будет использоваться/возвращаться.
ReadWriteLock lock = map.computeIfAbsent(key, ReentrantReadWriteLock::new)
Обычно для синхронизации используется синхронизация. Простейшей формой этого является синхронизация самого метода.
public synchronized ReentrantReadWriteLock getLock(String tableName) {
Однако, если вас беспокоит производительность, я бы рассмотрел следующий подход, который использует синхронизированный блок, но только если начальная блокировка не найдена.
public ReentrantReadWriteLock getLock(String tableName) {
ReentrantReadWriteLock lock = locksMap.get(tableName);
if (lock != null) {
return lock;
}
synchronized(locksMap) {
lock = locksMap.get(tableName);
if (lock == null) {
lock = new ReentrantReadWriteLock();
locksMap.put(tableName, lock);
}
}
return lock;
}
lock = locksMap.get(tableName)
, поскольку JVM может оптимизировать две инструкции lock = locksMap.get(tableName)
.
Вы можете либо модифицировать метод для synchronized
либо добавить блок синхронизации внутри метода, охватывающий вызовы get()
и put()
. Обратите внимание, что существует псевдо-шаблон (я предпочитаю называть его идиомой) об этом, называемом Double-Check Locking.
Другой вариант - использовать ConcurrentMap
, который предлагает метод putIfAbsent()
.
Обратите внимание, что вы столкнетесь с большим количеством обсуждений/обсуждений о производительности различных опций. Я призываю вас прочитать их со здоровым зерном соли. Микрооптимизация и анализ производительности - опасная территория, и часто читаемость и ремонтопригодность кода намного превышают несколько микросекунд.
synchronized
блок, здесь