[Java] ConcurrentHashMapとHashMapの非互換性

2020年1月27日月曜日

Java パフォーマンス マルチスレッド

Effective Javaによると

Effective Java 第3版の項目81 "waitとnotifyよりも並行処理ユーティリティを選ぶ"によるとConcurrentHashMap.putIfAbsentをそのまま使うよりも、
public static String intern(String s) {
    String previousValue = map.putIfAbsent(s, s);
    return previousValue == null ? s : previousValue;
}

一度ConcurrentHashMap.getで要素の存在を確認してからputIfAbsentを呼び出したほうが、高速化するというテクニックが出てくる。
public static String intern(String s) {
    String result = map.get(s);
    if (result == null) {
        result = map.putIfAbsent(s, s);
        if (result == null)
            result = s;
    }
    return result;
}

普通に考えると

項番81は本件が主題なのではなく、少し触れられている程度の話なのだけれど、ConcurrentHashMap.getのみであれば同期操作が発生しないためだろう。
そう考えると、ifの条件が成立する可能性が高い場合は、このテクニックを使うほうが速そうだし、逆の場合はかえって遅くなりそうな気がする。

完全に定数であれば、要素を追加して、Unmodifiableにした後は、同期する必要もなくgetするだけなので、このテクニックを使う必要はなさそう。
実際には一度計算したものを再計算したくないような場面で、キャッシュとしてConcurrentHashMapを使うときに役立つのかな。

ただし、マップに要素が存在しても値がnullの場合はifの条件が成立しないため、結局かえって遅くなるパターンになりそう。 こういう使い方はあまりないと思うけど。

ConcurrentHashMapとHashMapの非互換性

...と思ったんだけど、実際にConcurrentHashMapにnullの値を入れようとしたら、NullPointerExceptionが発生してしまった。
ConcurrentHashMapのAPI仕様を確認すると確かに次のように書いてある。

Hashtableと同様に(HashMapとは異なる)、このクラスは、キーまたは値としてnullが使用されることを許可しません。

うーん、これだと最初HashMapを使っていて、後からConcurrentHashMapに変えた場合に困ったことになってしまう可能性がある。
キーはともかく値としてもMapにnullを入れるデザインは最初からしないほうがいいってことかな。