が同期されていることはわかっていHashtable
ますが、そのget()
メソッドが同期されているのはなぜですか?
読み取り方法だけですか?
読み取りが同期されていない場合、読み取りの実行中に Hashtable が変更される可能性があります。新しい要素が追加されたり、基になる配列が小さすぎて、より大きな配列に置き換えられたりする可能性があります。順次実行しないと、これらの状況に対処することは困難です。
ただし、get
Hashtable が別のスレッドによって変更されたときにクラッシュしない場合でも、synchronized
キーワードにはもう 1 つの重要な側面があります。つまり、キャッシュの同期です。簡単な例を使用してみましょう。
class Flag {
bool value;
bool get() { return value; } // WARNING: not synchronized
synchronized void set(bool value) { this->value = value; }
}
set
同期されていますが、get
そうではありません。このクラスに対して 2 つのスレッド A と B が同時に読み取りと書き込みを行うとどうなりますか?
1. A calls read
2. B calls set
3. A calls read
ステップ 3 で、A がスレッド B の変更を確認することが保証されますか?
A は、古い値がまだ存在する別のキャッシュを使用する別のコアで実行されている可能性があるためです。したがって、B にメモリを他のコアと通信させ、A に新しいデータをフェッチさせる必要があります。
どうすればそれを強制できますか? スレッドが同期ブロックに出入りするたびに、暗黙的なメモリ バリアが実行されます。メモリ バリアにより、キャッシュが強制的に更新されます。ただし、ライターとリーダーの両方がメモリ バリアを実行する必要があります。そうしないと、情報が適切に伝達されません。
この例では、スレッド B はすでに同期メソッドを使用しているset
ため、そのデータ変更はメソッドの最後で伝達されます。ただし、A には変更されたデータが表示されません。解決策はget
同期化することであり、更新されたデータを強制的に取得する必要があります。
ハッシュテーブルのソースコードを見ると、非同期で問題を引き起こす可能性のある多くの競合状態を考えることができますget()
。
(私はJDK6のソースコードを読んでいます)
たとえば、arehash()
は空の配列を作成し、それをインスタンスvarに割り当て、table
古いテーブルのエントリを新しいテーブルに配置します。したがってget
、空の配列割り当ての後で、実際にエントリを配置する前に発生した場合は、テーブルにある場合でもキーを見つけることができません。
もう1つの例は、テーブルインデックスのリンクリストを反復するループがあり、反復の途中で再ハッシュが発生した場合です。また、ハッシュテーブルにエントリが存在していても、エントリが見つからない場合があります。
Hashtable
同期されているということは、クラス全体がスレッドセーフであることを意味します
内部ではHashtable
、get()メソッドが同期されるだけでなく、他の多くのメソッドも同期されます。そして特にput()メソッドはトムが言ったように同期されます。
読み取りメソッドは、変数の可視性と一貫性を確保するため、書き込みメソッドとして同期する必要があります。