Java HashSet потокобезопасен только для чтения? - PullRequest
27 голосов
/ 21 марта 2011

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

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

Ответы [ 7 ]

41 голосов
/ 21 марта 2011

Из Javadoc:

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

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

13 голосов
/ 21 марта 2011

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;
   }
}

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

11 голосов
/ 21 марта 2011

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

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

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

6 голосов
/ 21 марта 2011

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

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

2 голосов
/ 21 марта 2011

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

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

2 голосов
/ 21 марта 2011

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

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

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

0 голосов
/ 21 марта 2011

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

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