Java concurrency: является ли окончательное поле (инициализировано в конструкторе) потокобезопасным?

Может ли кто-нибудь сказать, является ли этот класс потокобезопасным или нет?

class Foo {

    private final Map<String,String> aMap;

    public Foo() {
        aMap = new HashMap<String, String>();
        aMap.put("1", "a");
        aMap.put("2", "b");
        aMap.put("3", "c");
    }

    public String get(String key) {
        return aMap.get(key);
    }

}

Изменить: Это моя ошибка, чтобы не уточнить вопрос. Согласно JMM FAQ:

Необходимо предоставить новую гарантию безопасности инициализации. Если объект правильно сконструирован (что означает, что ссылки на него не исчезают во время построения), то все потоки, которые видят ссылку на этот объект, также будут видеть значения для своих окончательных полей, которые были установлены в конструкторе, без необходимости синхронизации.

Это заставило меня смутить, что набор в aMap равен aMap = new HashMap<String, String>();. Таким образом, другие потоки могут видеть эти

aMap.put("1", "a");
aMap.put("2", "b");
aMap.put("3", "c");

или нет?

Изменить: я нашел этот question, который точно закрыт для моего вопроса

Ответ 1

Как уже указывалось, он абсолютно потокобезопасен, а final важен здесь из-за его видимых эффектов памяти.

Присутствие final гарантирует, что другие потоки будут видеть значения на карте после завершения конструктора без какой-либо внешней синхронизации. Без final он не может быть гарантирован во всех случаях, и вам нужно будет использовать безопасные идиомы публикации при создании вновь созданного объекта для других потоков, а именно (от Java Concurrency на практике):

  • Инициализация ссылки на объект из статического инициализатора;
  • Сохранение ссылки на него в поле volatile или AtomicReference;
  • Сохранение ссылки на него в конечное поле правильно построенного объекта; или
  • Сохранение ссылки на него в поле, которое должным образом защищено блокировкой.

Ответ 2

Да, это так. Невозможно изменить ссылку aMap самостоятельно или добавить к карте после конструктора (запрет на отражение).

Если вы выставляете aMap, это не будет, потому что два потока могут затем изменить карту в одно и то же время.

Вы можете улучшить свой класс, сделав aMap немодифицируемым через Collections.unmodifiableCollection или Collections.unmodifiableMap.

Ответ 3

Guava имеет неизменные классы, которые упрощают и гарантируют неизменность такого рода:

private final ImmutableMap<String, String> aMap = ImmutableMap.of(
    "1", "a",
    "2", "b",
    "3", "c");

Ответ 4

Да, это, , если это все определение класса, а не его фрагмент.

Ключевым фактом является то, что содержимое aMap не может быть изменено после построения.

Ответ 5

У этого класса нет проблемы concurrency, потому что вы обнаруживаете только метод get. Если вы добавите какой-либо метод, который изменит карту, вы должны пометить этот метод как synchronized.

Ответ 6

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

Ответ 7

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

aMap = new HashMap<String, String>();

Как показано в http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html,

class FinalFieldExample {
    final int x;
    int y;
    static FinalFieldExample f;
    public FinalFieldExample() {
      x = 3;
      y = 4;
    }

    static void writer() {
       f = new FinalFieldExample();
    }

   static void reader() {
     if (f != null) {
       int i = f.x; // x is guaranteed to be 3
       int j = f.y; // y can have any value 
     }
   }
}

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

aMap.put("1", "a");
aMap.put("2", "b");
aMap.put("3", "c");

ИЗМЕНИТЬ. Я плохо видел комментарии ниже кода позже

Возможность видеть правильно построенное значение для поля хороша, но если само поле является ссылкой, то вы также хотите, чтобы ваш код отображал обновленные значения для объекта (или массива), на который он указывает, Если ваше поле является конечным полем, это также гарантировано. Таким образом, вы можете иметь конечный указатель на массив и не беспокоиться о других потоках, видящих правильные значения для ссылки на массив, а неверные значения для содержимого массива. Опять же, под "правильным" здесь мы подразумеваем "актуальность по состоянию на конец конструктора объекта", а не "последнее доступное значение".