2012年9月30日日曜日

スレッド間で共有される変数にはintを使うなかれ。AtomicIntegerを使うべし。

うっかり忘れそうなのでメモ。

【ダメダメな例】
MyThread package org.yyama;

public class MyThread extends Thread {
    public static final int LOOP = 10000000; // 1000万回 ループ
    public static int counter = 0;

    @Override
    public void run() {
        for (int i = 0; i < LOOP; i++)
            counter++;
    }
}

Main package org.yyama;

public class Main {
    private static final int THREAD_COUNT = 2; // スレッド2個で調査

    public static void main(String[] args) throws Exception {
        // MyThreadの配列を作成
        MyThread[] t = new MyThread[THREAD_COUNT];
     
        for (int j = 0; j < THREAD_COUNT; j++) {
            // MyThreadのインスタンスを作成し、実行
            t[j] = new MyThread();
            t[j].start();
        }
     
        // 全スレッドの処理が終わるまで待つ
        while (Thread.activeCount() != 1) {
            Thread.sleep(100);
        }
     
        // 結果と期待値を出力
        System.out.println("result  :" + MyThread.counter);
        System.out.println("expected:" + (MyThread.LOOP * THREAD_COUNT));
    }
}

実行結果result  :14190209expected:20000000

インクリメントしようとして、インクリメントできていないときが600万回くらいあります。
「int型をインクリメントするくらいで、スレッド間の競合を考えなくてもいいだろ。」という考えはスパっと切り捨てましょう。


【正しい例】
MyThreadだけ書き換えます。

MyThreadpackage org.yyama;
import java.util.concurrent.atomic.AtomicInteger;
public class MyThread extends Thread {    public static final int LOOP = 10000000; // 1000万回 ループ    public static AtomicInteger counter = new AtomicInteger(0);
    @Override    public void run() {        for (int i = 0; i < LOOP; i++)            counter.getAndIncrement();    }}


今度は大丈夫です。
実行結果result  :20000000expected:20000000


【もうひとつの正しい例】
もちろんsynchronizedを使ってもかまいません。
ただし、処理時間でAtomicIntegerに負けるそうです。

また、MyThreadのみ変更します。

MyThreadpackage org.yyama;
public class MyThread extends Thread {    public static final int LOOP = 10000000; // 1000万回 ループ    public static int counter = 0;
    @Override    public void run() {        increment();    }
    private static synchronized void increment() {        for (int i = 0; i < LOOP; i++)            counter++;    }}
これでも期待通りの結果が得られます。

参考にさせていただきました:
AtomicIntegerを追ってみた