LoginSignup
5
6

More than 5 years have passed since last update.

volatile変数をインクリメントしてはいけない

Posted at

問題提起

Java逆引きレシピを読んでたら

Count.java
Class Count {
  private volatile int count;

  public void increment() {
    count++;
  }

  @Override
  public String toString() {
    return "count: " + count;
  }
}

というようなコード例が見られたが、synchronizedされていない状態でvolatile変数をインクリメント、もしくはデクリメントするのはやめよう。

原因

インクリメントもしくはデクリメントはatomicな操作ではないため。もう少し具体的に書くと、インクリメントの動作の流れは以下の通り。

  1. 現在の値を取得
  2. 1.で取得した値に+1した値を代入

ここで、1と2の操作の間に別のスレッドによって何らかの値が代入されると、想定外の動作となってしまう。

修正方法

AtomicIntegerAtomicLongという便利なクラスが標準で用意されているので、複数スレッドからsynchronizedなしに参照される値には、もれなくこちらを使いましょう。

Count.java
import java.util.concurrent.atomic.AtomicInteger;

Class Count {
  private AtomicInteger count = new AtomicInteger();

  public void increment() {
    count.incrementAndGet();
  }

  @Override
  public String toString() {
    return "count: " + count.get();
  }
}

Java8以降では、LongAdderというクラスも用意されています。JavaDocによると、以下のような特徴があるようです。

This class is usually preferable to AtomicLong when multiple threads update a common sum that is used for purposes such as collecting statistics, not for fine-grained synchronization control.

This class extends Number, but does not define methods such as equals, hashCode and compareTo because instances are expected to be mutated, and so are not useful as collection keys.

後半は注意ですね。HashMapのKeyに入れたりするのはやめましょう。

歯止め

先ほどのコードで静的解析ツールのFindbugsを使うと警告が出るので、導入するのもよいと思います。この件以外にも様々なバグが見つかるので非常に便利です。

おわりに

こんな誤植を見つけてしまいましたが、良い本だと思います。AtomicInteger, AtomicLongの説明も載っていますし、LongAdderはこの本で初めて知りました。そもそも、逆引きはすごい便利です。Kindle版もあるよ。

5
6
1

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
5
6