2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

HashMap,TreeMapのcomputeIfAbsentでConcurrentModificationException

Posted at

事の始まり

とあるプロジェクトでJavaのバージョン更新(8→17)を行っていた時のこと。
java.util.HashMapに対してcomputeIfAbsentを呼び出しているところでConcurrentModificationExceptionが発生するようになってしまいました。
この部分は旧バージョンから特にコードをいじっていないところ、かつ複数スレッドから参照されるものではなかったのでJavaのバージョンに起因するものでないかと考えました。

原因のコード

調査すると同じHashMapのインスタンスに対してcomputeIfAbsentが入れ子になっていることがわかりました。
簡単に書くと以下のようなコードです。

Map<String, String> map = new HashMap<>();
map.computeIfAbsent("k1", tk1 -> map.computeIfAbsent(tk1, tk2 -> "value1"));

実際には"k1"というキーに"value1"が入るだけなので入れ子になっている意味がまったくないコードなのですが、Java8では動作していました。

※なお、内側のcomputeIfAbsentに渡すキーをtk1から"k2"のような外側と違うキーにしても同じ結果となることを確認しています。

実験と結果

Java8では動作していたコードがJava17でConcurrentModificationExceptionが出てしまったことから、どのバージョンから動作(内部実装)が変わっているのか実験してみることにしました。
なお、HashMapの内部実装でのExceptionだったことからTreeMapでも実験を行っています。

以下が結果です。(〇はエラーなし。×はConcurrentModificationException発生)

Javaバージョン HashMap TreeMap
8
9 ×
10 ×
11 ×
12 ×
13 ×
14 ×
15 × ×
16 × ×
17 × ×

HashMapは9から変わっているのに対してTreeMapは15からなのがちょっと意外でした。

まとめ

Javaの更新ドキュメントをすべてチェックしたわけではないので見逃している可能性はありますが、これに関してはAPIの仕様変更というより内部処理変更に伴う副作用(?)のように見えました。

今回Javaのバージョンによって挙動が変わったことから調査しましたが、そもそも元のコードに問題(computeIfAbsentを入れ子にする必要がない)があるので、実際のプロジェクトではコード側を修正してどのバージョンのJavaでも動くようにしています。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?