синхронизировать метод путем достижения лучшей производительности? - PullRequest
1 голос
/ 06 мая 2019

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

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

Теперь, чтобы сделать потокобезопасным, я планировал синхронизировать метод add, но это снизит производительность. Есть ли способ улучшить производительность без синхронизации с методом add?

class Test {
  private final Map<Integer, Integer> map = new ConcurrentHashMap<>();

  public void add(int key, int value) {
    if (map.containsKey(key)) {
      int val = map.get(key);
      map.put(key, val + value);
      return;
    }
    map.put(key, value);
  }

  public Object getResult() {
    return map.toString();
  }
}

Ответы [ 3 ]

4 голосов
/ 06 мая 2019

, но это ухудшит производительность

Скорее всего, не будет уничтожить производительность.Это немного уменьшит его, с дальнейшим уменьшением, если будет высокая частота столкновений.

Есть ли какой-нибудь лучший способ, которым мы можем достичь лучшей производительности?

Да, используйте merge() (Java 8+).Цитирование javadoc:

Если указанный ключ еще не связан со значением или связан с нулем, связывает его с данным ненулевым значением.В противном случае заменяет связанное значение результатами данной функции переназначения или удаляет, если результат равен нулю.

Пример:

public void add(int key, int value) {
    map.merge(key, value, (a, b) -> a + b);
}

Илииспользуя ссылку на метод до sum(int a, int b) вместо лямбда-выражения :

public void add(int key, int value) {
    map.merge(key, value, Integer::sum);
}
3 голосов
/ 06 мая 2019

Используйте merge :

class Test {
    final Map<Integer, Integer> map = new ConcurrentHashMap<>();

    public void add(int key, int value) {
        map.merge(key, value, Integer::sum);
    }

    public Object getResult() {
        return map.toString();
    }
}

Решение Java 7, если вы абсолютно не можете использовать синхронизированный (или вы не можете явно заблокировать):

class Test {
    final Map<Integer, AtomicInteger> map = new ConcurrentHashMap<>();

    public void add(int key, int value) {
        get(key).addAndGet(value);
    }

    private AtomicInteger get(int key) {
        AtomicInteger current = map.get(key);

        if (current == null) {
            AtomicInteger ai = new AtomicInteger();

            current = map.putIfAbsent(key, ai);

            if (current == null) {
                current = ai;
            }
        }

        return current;
    }

    public Object getResult() {
        return map.toString();
    }
}
1 голос
/ 07 мая 2019

synchronized вызывает узкое место только при выполнении дорогостоящей операции с блокировкой.
В вашем случае добавив synchronized вы делаете:
1. проверьте хеш-карту на наличие ключа
2. получить значение, сопоставленное с этим ключом
3. сделайте дополнение и поместите результат обратно в hashmap.

Все эти операции очень дешевы O(1), и если вы не используете какой-то странный шаблон для ключей, которые являются целыми числами, очень маловероятно, что вы сможете получить некоторую вырожденную производительность из-за коллизий.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...