Является Java HashSet потокобезопасным только для чтения?

Если у меня есть экземпляр HashSet после того, как я запустил его через Collections.unmodifiableSet(), он поточно-безопасен?

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

Ответ 1

Из Javadoc:

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

Чтение не изменяет набор, поэтому вы в порядке.

Ответ 2

HashSet будет потокобезопасным, если он используется только для чтения. Это не означает, что любой набор, который вы передадите в Collections.unmodifiableSet(), будет потоковым.

Представьте себе эту наивную реализацию contains, которая кэширует последнее проверенное значение:

Object lastKey;
boolean lastContains;

public boolean contains(Object key) {
   if ( key == lastKey ) {
      return lastContains;
   } else {
      lastKey = key;
      lastContains = doContains(key);
      return lastContains;
   }
}

Очевидно, что это не было бы потокобезопасным.

Ответ 3

Он был бы потокобезопасным, но только из-за того, что Collections.unmodifiableSet() внутренне издает цель Set безопасным образом (через поле final).

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

Возможно (теоретически), что из-за переупорядочения операции ссылка на этот объект только для чтения станет видимой для других потоков, прежде чем объект будет полностью инициализирован и заполнен данными. Чтобы устранить эту возможность, вам нужно безопасно публиковать ссылки на объект, например, сохраняя их в полях final, как это сделано с помощью Collections.unmodifiableSet().

Ответ 4

Каждая структура данных является потокобезопасной, если вы ее не мутируете.

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

Ответ 5

Да, это безопасно для одновременного доступа к чтению. Вот соответствующее предложение из документации:

Если несколько потоков обращаются к хеш-набору одновременно, и по крайней мере один из потоков изменяет набор, он должен быть синхронизирован снаружи.

В нем указано, что вам нужно только синхронизировать, если поток at least one изменяет его.

Ответ 6

Я не считаю, что это потокобезопасность только потому, что вы запускаете Collections.unmodifiableSet(). Несмотря на то, что HashSet, если он полностью инициализирован, и вы отметили его как не поддающийся модификации, не означает, что эти изменения будут видны другим потокам. Хуже того, при отсутствии синхронизации компилятору разрешено переупорядочивать инструкции, что может означать, что поток чтения не только видит недостающие данные, но также может видеть хешсет в странном состоянии. Поэтому вам понадобится синхронизация. Я считаю, что один путь вокруг этого - создать хэшсет как окончательный и полностью инициализировать его в конструкторе. Вот хорошая статья о JMM http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html. Прочтите раздел о том, как последние поля работают под новым JMM?

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

Ответ 7

Если разделяемая память никогда не будет изменена, вы всегда можете читать без синхронизации. Создание немодифицируемого набора просто обеспечит соблюдение того факта, что записи не могут быть сделаны.