web-dev-qa-db-ja.com

最終的な静的変数はJavaでスレッドセーフですか?

私はかなり読みましたが、決定的な答えは見つかりませんでした。

次のようなクラスがあります。

    public class Foo() {

        private static final HashMap<String, HashMap> sharedData;

        private final HashMap myRefOfInnerHashMap;

        static {
           // time-consuming initialization of sharedData
           final HashMap<String, String> innerMap = new HashMap<String, String>;
           innerMap.put...
           innerMap.put...
           ...a

           sharedData.put(someKey, Java.util.Collections.unmodifiableMap(innerMap));
        }

        public Foo(String key) {
            this.myRefOfInnerHashMap = sharedData.get(key);
        }

        public void doSomethingUseful() {
            // iterate over copy
            for (Map.Entry<String, String> entry : this.myRefOfInnerHashMap.entrySet()) {
                ...
            }
        }
     }

そして、FooのインスタンスからsharedDataにアクセスするのがスレッドセーフかどうか疑問に思っています(コンストラクターとdoSomethingUseful()に示されているように)。 Fooの多くのインスタンスは、マルチスレッド環境で作成されます。

私の意図は、sharedDataが静的初期化子で初期化され、その後は変更されないことです(読み取り専用)。

私が読んだことは、不変オブジェクトは本質的にスレッドセーフであるということです。しかし、これはインスタンス変数のコンテキストのように見えるものでしか見たことがありません。不変の静的変数はスレッドセーフですか?

私が見つけたもう1つの構造はConcurrentHashMapでした。私はタイプsharedCurrentHashMapのsharedDataを作成できますが、それに含まれるHashMapsもタイプConcurrentHashMapである必要がありますか?基本的に..

private static final ConcurrentHashMap<String, HashMap> sharedData;

または

private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;

それともより安全ですか(単にclone()するほうがコストがかかります)?

this.myCopyOfData = sharedData.get(key).clone();

TIA。

(静的初期化子は、より多くのコンテキストを提供するために編集されています。)

28
pschang

最終的なsharedDataへのreferenceは変更できないため、スレッドセーフです。マップの内容は[〜#〜] not [〜#〜]スレッドセーフです。これは、できればGuava ImmutableMap実装またはJava.util.Collections.unmodifiableMap()でラップする必要があるためですまたは、Java.util.concurrentパッケージのMap実装の1つを使用します。

[〜#〜] both [〜#〜]を実行した場合にのみ、マップ上で包括的なスレッドセーフが得られます。含まれているマップはすべて、不変であるか、並行実装の1つである必要があります。

.clone()は根本的に壊れているので近づかない

デフォルトのクローンは浅いクローンであり、完全なコピーではなくコンテナオブジェクトへの参照を返すだけです。理由については、一般に入手可能な情報に詳しく記載されています。

24
user177800

静的初期化ブロック内の静的最終フィールドの初期化は、スレッドセーフです。ただし、静的最終参照が指すオブジェクトはnotになる可能性があることに注意してください。参照先のオブジェクトがスレッドセーフである(たとえば、それが不変である)場合は、問題はありません。

質問で提案されているようにConcurrentHashMapを使用しない限り、外側のHashMapに含まれる個々のHashMapがスレッドセーフであるとは限りません。スレッドセーフな内部HashMap実装を使用しない場合、2つのスレッドが同じ内部HashMapにアクセスすると、意図しない結果が生じる可能性があります。 ConcurrentHashMapの一部の操作のみが同期されることに注意してください。たとえば、反復はスレッドセーフではありません。

8
Mike Daniels

スレッドセーフとは何ですか?もちろん、HashMapの初期化は、すべてのFooが同じMapインスタンスを共有し、静的なinitで例外が発生しない限り、Mapが存在することが保証されているという点で、スレッドセーフです。

しかし、マップのコンテンツを変更することは、スレッドセーフではありません。 static finalは、マップsharedDataを別のマップに切り替えることができないことを意味します。しかし、マップの内容は別の問題です。特定のキーを同時に複数回使用すると、同時実行性の問題が発生する可能性があります。

6
extraneon

はい、これもスレッドセーフです。静的クラスのすべての最終メンバーは、スレッドがアクセスできるようになる前に初期化されます。

初期化中にstaticブロックが失敗した場合、ExceptionInInitializerErrorが最初に初期化を試行するスレッドで発生します。その後クラスを参照しようとすると、NoClassDefFoundErrorが発生します。

一般に、HashMapの内容は、スレッド間の可視性を保証しません。ただし、クラス初期化コードはsynchronizedブロックを使用して、複数のスレッドがクラスを初期化しないようにします。この同期により、マップ(およびマップに含まれるHashMapインスタンス)の状態がフラッシュされ、すべてのスレッドで正しく表示されます(マップまたはマップに含まれるマップに変更が加えられていない場合)。クラス初期化子の外。

クラスの初期化と同期の要件については、 Java言語仕様、§12.4.2 を参照してください。

5
erickson

いいえ。それらが不変である場合を除きます。

彼らがする唯一のことは

  • クラスレベルでアクセスできる
  • 参照を変更しないでください。

それでも属性が変更可能な場合は、スレッドセーフではありません。

参照: 最終的なインスタンス変数を同期しますか?

クラスレベルであることを除いて、まったく同じです。

5
OscarRyz

final static変数について、本質的にスレッドセーフなものはありません。メンバー変数final staticを宣言すると、この変数が1回だけ割り当てられることが保証されます。

スレッドセーフティの問題は、変数の宣言方法とは関係がありませんが、変数の操作方法に依存しています。したがって、プログラムの詳細なしに質問に答えることは実際には不可能です。

  • 複数のスレッドがsharedData変数の状態を変更しますか?
  • その場合、sharedDataのすべての書き込み(および読み取り)で同期しますか?

ConcurrentHashMapを使用すると、Mapの個々のメソッドがスレッドセーフであることが保証されるだけであり、このようなスレッドセーフな操作は行われません。

if (!map.containsKey("foo")) {
    map.put("foo", bar);
}
3
matt b

sharedDataの静的初期化がスレッドセーフであり、1回だけ実行されるかどうかを実際に確認していませんか?

はい、そうです。

もちろん、ここの多くの人々は、sharedDataの内容は依然として変更できることを正しく指摘しています。

1
Thirler

この場合、sharedDataオブジェクトのみが不変です。つまり、常に同じオブジェクトで作業することになります。しかし、その中のデータはいつでも、どのスレッドからでも変更(削除、追加など)できます。

0
Igor Artamonov