並列コレクションを使う

「同期化コレクション」を積極的に使う理由は見当たらないので、よっぽどでない限りは「並列コレクション」を使う。

執筆時バージョン
Java

Java SE 8

Javaでは「並列コレクション」と「同期化コレクション」があるが、後発の「並列コレクション」の方がはるかに良い設計になっている。「同期化コレクション」と聞くと安心したくなるが、危険があるので使ってはいけない。

「同期化コレクション」と比較しつつ「並列コレクション」の特徴をまとめる。

並列コレクションと同期化コレクション

同期化コレクションとは、「Vector」や「Hashtable」や「Collections.synchronizedXXX()による同期化ラッパー」のこと。要素の内容の変更があるようなメソッドは、全てsynchronizedになっているのが特徴。

並行コレクションとは「CopyOnWriteArrayList」や「ConcurrentHashMap」などJavaSE5.0より加わった「java.util.concurrent」以下にあるコレクションのこと。マルチスレッドによる並行アクセスを考慮した設計になっている。

同期化コレクションより並行コレクションの方が優れている点は次の2つ。

  • 複合アクションに対応している。

  • イテレーション時にConcurrentModificationExceptionを発生させない。

というよりかは、同期化コレクションを積極的に使う理由は見当たらないのでよっぽどでない限りは並列コレクションを使う。

並列コレクションの優れているところ

複合アクションに対応している

例えば同期化コレクションの「Vector」を、次のように利用するとスレッドセーフではなくなる。

Vectorがスレッドセーフでなくなる例
if(!vector.contains(element))
  vector.add(element);

この例ではcontains()からadd()仕様としたときに、他のスレッドがadd()するとおかしくなる。contains()もadd()もsynchronizedではあるが結局は「チェック・ゼン・アクト」と同じ状況になる。(状態をチェックしている間に変更される)

同期化コレクションと聞くと安心したくなるが「チェック・ゼン・アクト」や「リード・モディファイ・ライト」などのアクションが重なる(複合アクション)場合は、スレッドセーフにならなくなることが多い。

これは並行コレクションでも使い方によっては当てはまる。ただ並行コレクションでは、一般的によく使われるであろう複合アクションについてメソッドとして用意されている。そのメソッドはもちろんロックなどの同期化処理をされているのでスレッドセーフになっている。

上記の例だけを解決したければ、CopyOnWriteArrayListのaddIfAbsent()を使えばOK。

イテレーション時にConcurrentModificationExceptionを発生させない

コレクション関連のマルチスレッド環境で考慮しなければならないことに、イテレーション時のConcurrentModificationExceptionがある。これは「イテレートしている最中に他のスレッドによってコレクションが変更される」と投げられる例外。

シングルスレッドでは意識することがないが、マルチスレッドではコレクション実装によってはロックなどしなくてはいけなくなる。

並列コレクションはもちろんこれに対応している。方式としてはコレクションのクローンを作って、そのクローンをイテレーションするというやり方。これによって若干パフォーマンスは落ちるがConcurrentModificationExceptionが発生する危険が全くなくなる。

Appendix B: 改訂履歴

  • v1.0, 2015-02-26: 初稿