スレッドセーフではないコード(2回目)

前回(1回目)サーブレットを例にスレッドセーフではないコードを考えてみたが、スレッドセーフではないことを目に見えるかたちで実証するのが難しかった。
今回はJava SEの環境で動かせるクラスでスレッドセーフではないことを実証してみる。

まずは、カウントを返すクラスを用意する。

/**
 * スレッドセーフでない順序数生成メソッド
 */
public class UnsafeSequence {
	/**
	 * カウンタ
	 */
	private int nextValue;

	/**
	 * 重複のない数を返す
	 */
	public int getNext() {
		return nextValue++;
	}
}

次に、このカウンターを使うクラスを用意する。mainを起動すると650個のスレッドが立ち上がり、各スレッドは100回カウンターを呼び出す。

public class ThreadMain {
	public static void main(String[] args) {
		UnsafeSequenceCallerThread callerThread = new UnsafeSequenceCallerThread();
		for (int i = 0; i < 650; i++) {
			Thread thread = new Thread(callerThread);
			thread.start();
		}
	}
}

class UnsafeSequenceCallerThread implements Runnable {
	UnsafeSequence unsafeSequence = new UnsafeSequence();

	public void run() {
		for (int i = 0; i < 100; i++) {
			int sequence = unsafeSequence.getNext();
			System.out.println(sequence);
		}

	}
}

mainを実行した結果はどうなっているだろうか?出力結果を降順にソートしてみてほしい。実行環境やタイミングによって異なるが、同じ数値が2度3度と出力され、一番大きい数が「64999」ではないハズだ。私の環境では「64997」だった。

では、期待通り動かすにはどうすればよいか?一番簡単なのはgetNextメソッド自体をsynchronizedにすること。これにより、nextValueにアクセスする唯一のgetNextメソッドがロックによってガードされることになる。

ちなみに、nextValueをvolatileにしてもダメ。nextValue++の++が非アトミックな操作なので。

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

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