並行コレクション
概要
Java はマルチスレッド環境で安全にデータを操作するための様々なスレッドセーフなコレクションクラスを提供している。
スレッドセーフオプション比較
| クラス | スレッドセーフ | 説明 |
|---|---|---|
| ArrayList | いいえ | 非同期、最高の性能 |
| Vector | はい | 同期、性能が低い、非推奨 |
| Collections.synchronizedList() | はい | ラッパー方式 |
| CopyOnWriteArrayList | はい | 読み書き分離、読み多く書き少ない場合に適する |
BlockingQueue ブロッキングキュー
BlockingQueue はブロッキング操作を対応する FIFO キュー。
いつスレッドをブロックするか、いつスレッドを起床させるかを気にする必要がない。全て BlockingQueue が処理する。
実装クラス
| 実装クラス | 説明 |
|---|---|
ArrayBlockingQueue | 有界ブロッキングキュー、配列ベース |
LinkedBlockingQueue | 有界ブロッキングキュー、リンクリストベース |
DelayQueue | 遅延無界優先度ブロッキングキュー |
PriorityBlockingQueue | 優先度ソート対応の無界ブロッキングキュー |
SynchronousQueue | バッファなしの待機キュー、要素を格納しないブロッキングキュー |
操作メソッド
| タイプ | 例外スロー | 特殊値 | ブロッキング | タイムアウト |
|---|---|---|---|---|
| 挿入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
| 削除 | remove() | poll() | take() | poll(time, unit) |
| 検査 | element() | peek() | - | - |
動作説明:
- 例外スロー:
- キューが満杯の時、
add()はIllegalStateException: Queue fullをスロー - キューが空の時、
remove()はNoSuchElementExceptionをスロー
- キューが満杯の時、
- 特殊値:
offer()成功でtrue、失敗でfalseを返すpoll()成功で要素、失敗でnullを返す
- ブロッキング:
- キューが満杯の時、
put()は空きができるまでブロック - キューが空の時、
take()は要素ができるまでブロック
- キューが満杯の時、
- タイムアウト:
- 指定時間ブロック後、タイムアウトで終了
ConcurrentHashMap
ConcurrentHashMap はスレッドセーフな HashMap 実装。
| 特性 | 説明 |
|---|---|
| 実装方式 | CAS + Synchronized(内部実装でロックの粒度を最小に維持) |
| デフォルトセグメント数 | 16 segments |
| デフォルト容量 | 16 |
| 負荷係数 | 0.75 |
ConcurrentHashMap は複数のスレッドが異なるセグメントに同時アクセスできるため、並行性能が大幅に向上する。Hashtable とは異なり、Map 全体をロックしない。
ConcurrentHashMapExample.java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 1);
map.putIfAbsent("key2", 2); // アトミック操作
map.compute("key1", (k, v) -> v + 1); // アトミック操作CopyOnWriteArrayList
CopyOnWriteArrayList はスレッドセーフな ArrayList 変種。
| 特性 | 説明 |
|---|---|
| 読み操作 | ロック不要、直接読み取り |
| 書き操作 | 元の配列をコピーし、コピーを修正してから置換 |
| 適用シーン | 読み多く書き少ない |
書き操作は配列全体をコピーする必要があり、オーバーヘッドが大きい。読み多く書き少ないシーンに適し、書き込みが頻繁なシーンには適さない。
CopyOnWriteArrayListExample.java
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("item1");
list.add("item2");
// イテレーション中に安全に修正可能
for (String item : list) {
System.out.println(item);
list.add("newItem"); // ConcurrentModificationException をスローしない
}スレッドセーフなデータ構造
以下は一般的な並行データ構造:
| データ構造 | 説明 |
|---|---|
ConcurrentHashMap | 並行 HashMap |
ConcurrentLinkedQueue | 無界非ブロッキングキュー |
ConcurrentLinkedDeque | 無界非ブロッキング両端キュー |
LinkedBlockingQueue | 有界ブロッキングキュー |
ArrayBlockingQueue | 有界ブロッキングキュー |
Lock-Free データ構造
技術原理
Lock-Free データ構造は主に CAS(Compare and Swap)操作を使用して実装される。
実装例
- Lock-Free Stack
- Lock-Free LinkedList
- Disruptor
- Ring Buffer
ConcurrentLinkedQueueConcurrentLinkedDequeAtomicXXXクラス(AtomicInteger, AtomicLong, AtomicReference など)
AtomicIntegerExample.java
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // アトミックインクリメント
counter.compareAndSet(1, 2); // CAS 操作ArrayList vs Vector
| 特性 | ArrayList | Vector |
|---|---|---|
| スレッドセーフ | いいえ | はい(同期) |
| 性能 | 良い | 悪い |
| 拡張戦略 | 通常 50% 増加または倍増 | 倍増、増加率指定可能 |
| 推奨度 | 一般シーンで推奨 | 非推奨、過去のクラス |
Collection のスレッドセーフ選択
flowchart TB
Q{スレッドセーフが必要?}
Q -->|いいえ| A[ArrayList / HashMap]
Q -->|はい| S{読み書き比率?}
S -->|読み多く書き少ない| C[CopyOnWriteArrayList]
S -->|読み書き均衡| CH[ConcurrentHashMap / Collections.synchronized*]
S -->|ブロッキング必要| B[BlockingQueue 系列]