Правильный вызов методов get () и put () Hashmap.

1

для кода ниже:

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 вызываются отдельно.

Любые решения с объяснением будут оценены? Заранее спасибо.

  • 3
    Возможно использовать ConcurrentHashMap? docs.oracle.com/javase/7/docs/api/java/util/concurrent/...
  • 0
    Вы можете использовать synchronized блок, здесь
Показать ещё 4 комментария
Теги:
multithreading
hashmap
concurrency

3 ответа

3
Лучший ответ

Использование ConcurrentMap обычно обеспечивает лучшую производительность, чем synchronized блок.

Java 5-7:

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;
}

Java 8+:

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 должна создать новый экземпляр блокировки при определенных обстоятельствах и что новый объект никогда не будет использоваться/возвращаться.

  • 2
    Согласовано. При использовании Java 8 использование computeIfAbsent () намного проще: ReadWriteLock lock = map.computeIfAbsent(key, ReentrantReadWriteLock::new)
0

Обычно для синхронизации используется синхронизация. Простейшей формой этого является синхронизация самого метода.

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;
}
  • 0
    Второй метод не является поточно- lock = locksMap.get(tableName) , поскольку JVM может оптимизировать две инструкции lock = locksMap.get(tableName) .
  • 0
    Вы одновременно читаете и пишете в HashMap, который не является поточно-ориентированным.
Показать ещё 2 комментария
0

Вы можете либо модифицировать метод для synchronized либо добавить блок синхронизации внутри метода, охватывающий вызовы get() и put(). Обратите внимание, что существует псевдо-шаблон (я предпочитаю называть его идиомой) об этом, называемом Double-Check Locking.

Другой вариант - использовать ConcurrentMap, который предлагает метод putIfAbsent().

Обратите внимание, что вы столкнетесь с большим количеством обсуждений/обсуждений о производительности различных опций. Я призываю вас прочитать их со здоровым зерном соли. Микрооптимизация и анализ производительности - опасная территория, и часто читаемость и ремонтопригодность кода намного превышают несколько микросекунд.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню