Java не финальный статус c Работа с картой в блоке c - PullRequest
0 голосов
/ 01 февраля 2020

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

class Car {
   public static Map<String, String> features = new HashMap<>();
   static {
       features.put("color", "red");
       features.put("foo", "bar");
   }
   public Comparable<?> getValue(String id) {
       if(!features.containsKey(id)) {
          features.put(id, id);
       }
       String res = features.get(id);
       // some business logic and return stmt.
   }
}

Недавно мы столкнулись с неожиданным поведением в нашем приложении, в котором значение, возвращаемое getValue("color"), было нулевым. Мне не удалось воспроизвести эту проблему, но, похоже, это произошло, когда два потока обрабатывались одновременно.

  1. Карта features изменяется только в методе getValue, если аргумент еще не доступен на карте.
  2. Произошла ошибка для аргумента, которым является карта инициализируется с помощью блока stati c.
  3. Пример использования - Car c = new Car(); c.getValue("color");

Любая помощь будет принята с благодарностью. Спасибо.

1 Ответ

4 голосов
/ 01 февраля 2020

Нет, это не потокобезопасно.

Из Javado c из HashMap:

Обратите внимание, что это реализация не синхронизирована. Если несколько потоков одновременно обращаются к карте ha sh, и хотя бы один из потоков структурно изменяет карту, она должна быть синхронизирована извне.

Итак, синхронизируйте доступ к карте:

String res;
synchronized (features) {
  if(!features.containsKey(id)) {
    features.put(id, id);
  }
  res = features.get(id);
}

и сделайте поле final, а также синхронизируйте внутри инициализатора stati c.

Или, лучше, используйте ConcurrentHashMap и метод computeIfAbsent.

String res = concurrentFeatures.computeIfAbsent(id, k -> id);

(HashMap также имеет метод computeIfAbsent в Java 8+, но вам нужно вызвать его в синхронизированном тоже блок).


На самом деле, еще лучший способ сделать это в Java 8+ - использовать getOrDefault, при условии, что вам на самом деле не нужно сохранять ранее невидимый ключ / пары значений:

res = features.getOrDefault(id, id);

Это не изменяет карту, поэтому вам не нужно беспокоиться о безопасности потока; вам просто нужно убедиться, что он инициализирован безопасно:

public final static Map<String, String> features;
static {
   Map<String, String> f = new HashMap<>();
   f.put("color", "red");
   f.put("foo", "bar");
   features = f;
}
...