4

私は最近、次の構造に出くわしました

Map<String,Value> map = new HashMap<>();
...
Value getValue(String key) {
    synchronized (key.intern()) {
        return map.remove(key);
    }
}

intern()通常はそれほど高速ではないことを考えると、これがsynchronized, Collections.synchronizedMap(Map)orを使用した場合よりも優れているとは思えませんConcurrentHashMap。しかし、この特定のケースでは、この構造が他のすべての方法よりも高速であったとしても、これは適切に同期されていますか? ハッシュテーブルの再編成中に削除が発生する可能性があるため、これがスレッドセーフであるとは思えません。しかし、これがうまくいくとしても、HashMap javadocが次のように述べていることを考えると、コードが壊れているのではないかと思います。

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

4

1 に答える 1

7

HashMapこれは、複数のスレッドからに安全にアクセスするには不十分です。実際、何かを壊すことはほぼ保証されています。特定のキーで同期することにより、別のスレッドが別のキーで動作している限り、マップは依然として危険な状態で同時に変更される可能性があります。

これら 3 つのスレッドが同時に実行しようとしていたかどうかを検討してください。

Thread 1                Thread 2                 Thread 3
synchronized("a") {     synchronized("a") {      synchronized("b") {
  map.remove("a");        map.remove("a");         map.remove("b");
}                       }                        }

スレッド 1 と 2 は、同じオブジェクト (Java インターン文字列定数) で同期しているため、互いに正しく待機します。しかし、スレッド 3 は、他のスレッドで進行中の作業によって妨げられず、他の誰もロックしていないため、すぐに同期ブロックに入ります"b"。現在、2 つの異なる同期ブロックがmap同時に相互作用しており、すべての賭けがオフになっています。やがて、あなたのHashMap意志は腐敗します。

Collections.synchronizedMap()はマップ自体を同期オブジェクトとして正しく使用するため、使用されているキーだけでなく、マップ全体をロックします。HashMapこれは、複数のスレッドからアクセスされるの内部破損を防止する唯一の強力な方法です。

ConcurrentHashMapマップ内のすべてのキーのサブセットを内部的にロックすることにより、投稿したコードがやろうとしていると思うことを正しく行います。そうすれば、複数のスレッドが異なるスレッドの異なるキーに安全にアクセスでき、互いにブロックすることはありませんが、キーがたまたま同じバケットにある場合、マップは引き続きブロックされます。この動作はconcurrencyLevelコンストラクタ引数で変更できます。

参照: Java 同期ブロックと Collections.synchronizedMap の比較


余談ですsynchronized(key.intern()) 、議論のために、これがに同時にアクセスする合理的な方法であると仮定しましょうHashMap。これでも、信じられないほどエラーが発生しやすくなります。アプリケーション内の 1 つの場所でキーの呼び出し.intern()に失敗した場合、すべてがクラッシュする可能性があります。

于 2014-12-09T23:21:51.103 に答える