Необходимость блокировок при работе с одновременной хэш-картой - PullRequest
0 голосов
/ 27 марта 2019

Вот код в одном из моих классов:

 class SomeClass {

   private Map<Integer, Integer> map = new ConcurrentHashMap<>();
   private volatile int counter = 0;
   final AtomicInteger sum = new AtomicInteger(0); // will be used in other classes/threads too
   private ReentrantLock l = new ReentrantLock();

   public void put(String some) {
    l.lock();
    try {
        int tmp = Integer.parseInt(some);
        map.put(counter++, tmp);
        sum.getAndAdd(tmp);
    } finally {
        l.unlock();
    }
   }

   public Double get() {
    l.lock();
    try {
        //... perform some map resizing operation ...
        // some calculations including sum field ...
    } finally {
        l.unlock();
    }
   }

}

Можно предположить, что этот класс будет использоваться в параллельной среде.

Вопрос: как вы думаете, есть ли необходимость в замках? Как этот код пахнет? :)

Ответы [ 2 ]

1 голос
/ 27 марта 2019

Давайте посмотрим на операции внутри public void put(String some).

  1. map.put(counter++, tmp);
  2. sum.getAndAdd(tmp);

Теперь давайте рассмотрим отдельные части.

  1. counter - переменная переменная.Таким образом, он обеспечивает только видимость памяти, но не атомарность.Поскольку counter++ является составной операцией, вам нужна блокировка для достижения атомарности.

  2. map.put(key, value) является атомарной, поскольку это ConcurrentHashMap.

  3. sum.getAndAdd(tmp) является атомарным, поскольку это AtomicInteger.

Как видите, кроме counter++ каждая другая операция является атомарной.Однако вы пытаетесь достичь какой-то функции, комбинируя все эти операции.Чтобы достичь атомарности на уровне функциональности, вам нужен замок.Это поможет вам избежать неожиданных побочных эффектов, когда потоки чередуются между отдельными атомарными операциями.

Так что вам нужна блокировка, потому что counter++ не является атомарным, и вы хотите объединить несколько атомарных операций для достижения некоторой функциональности ( при условии, что вы хотите, чтобы это было атомарно).

0 голосов
/ 27 марта 2019

Поскольку вы всегда увеличиваете counter, когда используете его в качестве ключа для ввода на эту карту:

map.put(counter++, tmp);

, когда вы приходите, чтобы прочитать его снова:

return sum / map.get(counter);

map.get(counter) будет null, так что это приводит к NPE (если вы не положите более 2 ^ 32 вещей на карту, ofc).(Я предполагаю, что вы имеете в виду sum.get(), иначе он не будет компилироваться).

Таким образом, вы можете иметь эквивалентную функциональность без каких-либо блокировок:

class SomeClass {
  public void put(String some) { /* do nothing */ }
  public Double get() {
    throw new NullPointerException();
  }
}

Выдействительно не удалось решить проблему с вашим edit .divisor по-прежнему будет нулевым, поэтому эквивалентная функциональность без блокировок будет:

class SomeClass {
  private final AtomicInteger sum = new AtomicInteger(0);

  public void put(String some) {
    sum.getAndAdd(Integer.parseInt(some));
  }

  public Double get() {
    return sum.get();
  }
}
...