ConcurrentHashMapを使うならatomicなメソッドを使おう

ConcurrentHashMapを使うときの注意点

タイトルの通りです。

ConcurrentHashMapは同期を取りつつ、パフォーマンスも優れたとても優秀なやつで、最近Webアプリケーションで、キャッシュみたいなものを作るときに使いました。

とはいえ同期を取るとはいっても以下のようなキーの存在を確認して、putするような複合アクションの呼び出し(いわゆるcheck-then-act)では同期されません。(別にこれは、従来のHahMapとかCollections.syncronizedMapとかでも同じです)

  // atomicではない操作。
    if(!map.containsKey(key)) {
        map.put(key, value);
    }

以下が実証コードで、実行すると高い確率で複数スレッドで同一キーへのputを行います。
(今回のケースだとあまり問題にならないですが、valueを更新するような操作だと更新内容が喪失する可能性はあります。
また同期化されない処理は後々厄介なバグを生んだりするそうです。参考資料より。)

Atomicな操作

で、こんな時のためにConcurrentHashMapには、キーがなければ値を追加するという上記のような操作を行うputIfAbsentメソッドがあります。

  // 上とほぼ同等な内容だが、atomicな操作。
    map.putIfAbsent(key, value)

atomicなメソッドはほかに、replace, removeなんかがあります。 というわけで、もし既存のソースでMapをConcurrentHashMapに置き換える時は、上記のようは複合アクションを行っていないかも確認した方が良いという話でした。

参考資料

Java並行処理プログラミング ―その「基盤」と「最新API」を究める―

Java並行処理プログラミング ―その「基盤」と「最新API」を究める―