コレクションビューを利用して繰返し処理を行う場合、コレクションオブジェクトを使った同期が失敗する場合があることについて、Java API 仕様の java.util.Collections の解説に次のような警告が書かれている。[API 2006]
コレクションビューを使用して繰返し処理を行う場合、ユーザは、返されるマップを使用して同期を行う必要がある。...これに従わない場合、動作は不定となる。
ロックオブジェクトとして、基になるコレクションではなくコレクションビューを使用するクラスは、2つの異なるロック方式を使用してしまう恐れがある。基となるコレクションが複数のスレッドからアクセス可能である場合、コレクションビューを使って同期を行うクラスはスレッドセーフであるために必要な要件に違反し、それゆえスレッドセーフではない。したがって、コレクションビューに対する繰返し処理を同期する必要があり、かつビューの基になるコレクションにアクセスできるプログラムは、基になるコレクションを使って同期しなくてはならない。ビューを使った同期はこのルールの違反となる。
違反コード (コレクションビュー)
以下の違反コードでは、HashMapオブジェクトと、2つのビューオブジェクトを作成している。1つ目のビューオブジェクトは mapView フィールドによってカプセル化された空のHashMapの同期されたビューであり、2つ目のビューオブジェクトは setView フィールドによってカプセル化されたキーセットのビューである。以下のコードでは setView を使って同期を行っている。[Tutorials 2008]
private final Map<Integer, String> mapView = Collections.synchronizedMap(new HashMap<Integer, String>()); private final Set<Integer> setView = mapView.keySet(); public Map<Integer, String> getMap() { return mapView; } public void doSomething() { synchronized (setView) { // setView を使って間違った同期を行っている for (Integer k : setView) { // ... } } }
次の図に示すように、このコード例では、HashMapはmapViewによって表される同期マップの基になるコレクションを提供し、mapViewはsetViewの基になるコレクションを提供している。
図: 違反コード例におけるコレクションビューと基となるコレクションの関係
lck04-j-backing-collection.jpgHashMap にはアクセスできないが、mapView にはパブリックメソッド getMap() を使ってアクセスできる。synchronized 文は mapView ではなく setView の固有ロックを使用するため、他のスレッドは同期マップに変更を加えることができ、イテレータ k を無効にできる。
適合コード (コレクションのロックオブジェクト)
以下の適合コードでは、setViewではなくmapViewを使って同期している。
private final Map<Integer, String> mapView = Collections.synchronizedMap(new HashMap<Integer, String>()); private final Set<Integer> setView = mapView.keySet(); public Map<Integer, String> getMap() { return mapView; } public void doSomething() { synchronized (mapView) { // set ではなく map を使って同期している for (Integer k : setView) { // ... } } }
繰返し処理中、元になるマップを変更できないため、上記のコードはルールに適合している。
リスク評価
コレクションオブジェクトではなくコレクションビューを使って同期を行うと、予測できないプログラムの動作を引き起こす可能性がある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
LCK04-J | 低 | 中 | 中 | P4 | L3 |
参考文献
翻訳元
これは以下のページを翻訳したものです。
LCK04-J. Do not synchronize on a collection view if the backing collection is accessible (revision 80)