web-dev-qa-db-ja.com

Java HashSetは読み取り専用でスレッドセーフですか?

Collections.unmodifiableSet()を実行した後にHashSetのインスタンスがある場合、それはスレッドセーフですか?

Setのドキュメントにはそうではないと書かれているので、私はこれを求めていますが、私は読み取り操作のみを実行しています。

27
Asaf Mesika

Javadocから:

この実装は同期されていないことに注意してください。複数のスレッドが同時にハッシュセットにアクセスし、少なくとも1つのスレッドがセットを変更する場合は、外部で同期する必要があります。

読み取りによってセットが変更されることはないため、問題ありません。

41
Brian Roach

HashSetを読み取り専用で使用すると、スレッドセーフになります。 anyCollections.unmodifiableSet()に渡すセットがスレッドセーフになるという意味ではありません。

最後にチェックした値をキャッシュするcontainsのこの素朴な実装を想像してください。

Object lastKey;
boolean lastContains;

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

明らかにこれはスレッドセーフではありません。

13
Mark Peters

スレッドセーフですが、Collections.unmodifiableSet()がターゲットSetを内部的に安全な方法で(finalフィールドを介して)公開しているためです。

「読み取り専用オブジェクトは常にスレッドセーフである」などの一般的なステートメントは、操作の順序変更の可能性を考慮していないため、正しくありません。

(理論的には)操作の順序変更により、オブジェクトが完全に初期化されてデータが入力される前に、その読み取り専用オブジェクトへの参照が他のスレッドから見えるようになる可能性があります。この可能性を排除するには、オブジェクトへの参照を安全な方法で公開する必要があります。たとえば、Collections.unmodifiableSet()によって行われるように、finalフィールドに参照を格納します。

11
axtavt

変更しない限り、すべてのデータ構造はスレッドセーフです。

初期化するためにHashSetを変更する必要があるため、セットを初期化したスレッドとすべての読み取りスレッドの間で1回同期する必要があります。 one時間だけ実行する必要があります。たとえば、変更不可能なセットへの参照を、これまで触れたことのない新しいスレッドに渡します。

6
jmg

はい、同時読み取りアクセスに対して安全です。ドキュメントの関連する文は次のとおりです。

複数のスレッドがハッシュセットに同時にアクセスし、少なくとも1つのスレッドがそのセットを変更する場合は、外部で同期する必要があります。

同期する必要があるのはat least oneスレッドはそれを変更します。

2
Jonathan

Collections.unmodifiableSet()を実行したからといって、スレッドセーフであるとは思いません。 HashSetが完全に初期化されていて変更不可としてマークしたとしても、それらの変更が他のスレッドから見えるようになるわけではありません。さらに悪いことに、同期がない場合、コンパイラーは命令を並べ替えることができます。これは、読み取りスレッドで欠落データを確認できるだけでなく、ハッシュセットをより奇妙な状態で確認できることを意味します。したがって、同期が必要になります。これを回避する1つの方法は、ハッシュセットをfinalとして作成し、それをコンストラクターで完全に初期化することです。ここにJMMの良い記事があります http://www.cs.umd.edu/~pugh/Java/memoryModel/jsr-133-faq.html 。新しいJMMで最終フィールドがどのように機能するかに関するセクションを読んでください。

フィールドの正しく作成された値を表示する機能はいいですが、フィールド自体が参照である場合は、コードが、フィールドが指すオブジェクト(または配列)の最新の値も表示する必要があります。フィールドが最終フィールドである場合、これも保証されます。そのため、配列への最終的なポインタを取得でき、他のスレッドが配列参照の正しい値を見て、配列の内容が正しくないことを心配する必要はありません。繰り返しますが、ここで「正しい」とは、「使用可能な最新の値」ではなく、「オブジェクトのコンストラクターの終了時点で最新」を意味します。

2
richs