ConcurrentHashMap && Hashtable#
ConcurrentHashMap はどのようにスレッド安全を実現しているのか?#
JDK1.5 での実装#
Segment(extends ReentrantLock)
を使用して実現
分割ロック技術
を使用して、ConcurrentHashMap はロックをセグメントごとに保存し、各セグメントのデータにロック(segment)を割り当てます。あるスレッドが一つのロック(segment)を占有してそのセグメントのデータにアクセスしている間、他のセグメントのデータは他のスレッドによってもアクセス可能です。デフォルトで 16 個のセグメントが割り当てられ、Hashtable よりも効率が 16 倍向上します。
JDK1.8 での実装#
CAS
とsynchronized
を使用して並行安全を保証
JDK8 での実装もロック分離の思想を採用しており、ロックをsegment(JDK1.5)
よりも細かく分けています。ハッシュが衝突しなければ、並行してロックを取得することはありません。最初に無ロック操作CAS
でヘッドノードを挿入し、挿入に失敗した場合は他のスレッドがヘッドノードを挿入したことを示し、再度ループして操作を行います(スピンロックに似ています)。ヘッドノードが既に存在する場合は、synchronizedでヘッドノードロックを取得
し、後続の操作を行います。性能はセグメント分割ロックよりもさらに向上します。
Hashtable#
Hashtable もスレッド安全なキーと値のデータ構造です。
HashMap との比較:#
- Hashtable は JDK1.0 から存在し、HashMap は JDK1.2 で登場しました。
- Hashtable は Dictionary クラス(廃止)から継承され、HashMap は AbstractMap クラスから継承されています。
- Hashtable は Null キーも Null 値もサポートしていませんが、HashMap では null をキーとして使用でき、そのようなキーは一つだけです;一つまたは複数のキーに対して値が null であることができます。
- Hashtable はスレッド安全であり、すべてのメソッドに Synchronize メソッドが追加されていますが、HashMap はスレッド安全ではありません。
- Hashtable と HashMap はどちらも Iterator を使用していますが、歴史的な理由から Hashtable は Enumeration 方式も使用しています。
- Hashtable のデフォルトの初期サイズは 11 で、以降は毎回拡張され、容量は元の 2n+1 になります。HashMap のデフォルトの初期サイズは 16 で、以降は毎回拡張され、容量は元の 2 倍になります。
- ハッシュ値の計算方法が異なります:Hashtable はオブジェクトの hashCode を直接使用し、HashMap はさらに擾乱関数を使用して処理します。
Collections.synchronizedMap () との違い#
synchronizedMap
は Collections のプライベート静的内部クラスで、Collections.synchronizedMap(Map)
メソッドを通じて synchronizedMap を Map オブジェクトにアップキャストできます。
比較分析:
- Hashtable はすべてのメソッドに synchronized キーワードでロックをかけています。
- Object 型の mutext 排他オブジェクトメンバーがあり、スレッド安全なメソッドに対して synchronize キーワードで mutex にロックをかけています。