ConcurrentHashMap && Hashtable#
ConcurrentHashMap はどのようにスレッドセーフを実現しているのか?#
JDK1.5 での実装#
Segment(extends ReentrantLock)
を使用して実現
セグメントロック技術
を使用して、ConcurrentHashMap はロックをセグメントごとに保存し、各セグメントのデータにロック(segment)を割り当てます。あるスレッドが 1 つのロック(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 をキーとして使用でき、そのようなキーは 1 つだけ存在します;1 つまたは複数のキーに対応する値が null であることができます。
- Hashtable はスレッドセーフであり、すべてのメソッドに Synchronize メソッドが追加されていますが、HashMap はスレッドセーフではありません。
- Hashtable と HashMap はどちらも Iterator を使用していますが、歴史的な理由から Hashtable は Enumeration 方式も使用しています。
- Hashtable のデフォルトの初期サイズは 11 で、以降は毎回拡張されると容量は元の 2n+1 になります。HashMap のデフォルトの初期サイズは 16 で、以降は毎回拡張されると容量は元の 2 倍になります。
- ハッシュ値の計算方法が異なります:Hashtable はオブジェクトの hashCode を直接使用し、HashMap はさらに擾乱関数を使用して処理します。
Collections.synchronizedMap () との違い#
synchronizedMap
は Collections のプライベート静的内部クラスであり、Collecitons.synchronizedMap(Map)
メソッドを通じて synchronizedMap を Map オブジェクトにアップキャストすることができます。
比較分析:
- Hashtable はすべてのメソッドに synchronized キーワードでロックをかけています。
- Object 型の mutex 排他オブジェクトメンバーがあり、スレッドセーフなメソッドに対して synchronize キーワードで mutex にロックをかけています。