競り合い状態

シングルスレッドでは問題ないが、マルチスレッドの特定のタイミングと実行順で不正な結果になってしまう問題。

執筆時バージョン
Java

Java SE 8

概要

シングルスレッドでは問題ないが、マルチスレッドの特定のタイミングと実行順で不正な結果になってしまう問題。すなわちそれは特定のタイミングと実行順ではないと、正しい結果を得られないということ。

パターンとしては、次のようなものがある。

  • リード・モディファイ・ライト(read-modify-write)

  • チェック・ゼン・アクト(check-then-act)

  • チェック・イフ・アブセント(check-if-absent)

いずれも「状態をもとに行動を決める」のが特徴。よって前提となる状態が行動する前に変えられてしまうことでうまく動かない(これがマルチスレッドでは簡単に発生させることができてしまう)。また、複数のアクションがまとまって1つの意味をなしている「複合アクション」であることも特徴。

競り合い状態は、実行順によるところが大きく再現も難しいことが多い。これらのパターンを教訓として注意深く同期化や並行処理部品の必要性がないかを考えるのが基本的な対処になる。

リード・モディファイ・ライト(read-modify-write)

重複しない数値をgetIncrementedCount()から取る仕様とする。シングルスレッドでは問題ないがマルチスレッドではうまくいかないことがある。 .リード・モディファイ・ライトの例

public class UnsafeCounter {
    private long counter;

    public long getIncrementedCount() {
        return counter++;
    }
}

並行処理のバグがわかりにくいことを教えてくれるお手本のような例。これは「counter」が1つの行動に思えるが複数の行動をしているというのがポイント。「counter」は、「counterの値を読み込む」「counterの値に1を加える」「counterの値を更新する」という3つの行動をとっている立派な複合アクション。

よってマルチスレッド下では、値を更新する前に他のスレッドが値の読込をしてしまい、値の重複が発生してしまうことがある。

この例であれば、getIncrementedCount()にsynchronizedを付加するやAtomicLongを使うとことでマルチスレッド下でも思った通りに動く。

チェック・ゼン・アクト(check-then-act)

RaceConditionのインスタンスがシングルトンになる仕様とする。これもシングルスレッドでは問題ないがマルチスレッドではうまくいかないことがある。 .チェック・ゼン・アクトの例

public class RaceCondition {
    private static RaceCondition instatnce = null;

    public static RaceCondition getInstance(){
        if(instatnce == null)
            instatnce = new RaceCondition();
        return instatnce;
    }
}

一見すると正しく動く(インスタンスが1つに保たれる)気がするが、スレッドセーフではない典型的な例。次の順で実行されると、簡単にインスタンスが2つ(複数)のインスタンスが作られる。

Table 1. インスタンスが複数作成される手順
スレッドA スレッドB

1

instatnce == null→true

2

instatnce == null→true

3

instatnce = new RaceCondition();

4

instatnce = new RaceCondition();

このように状態をチェックして何かのアクションを行う場合、チェックしているすきに他のスレッドがアクションが起こる前の状態をチェックして意図しない状態に持ち込まれてしまうことになる。

この例を修正するには、メソッドにsynchronizedを指定すればよい。(他にも方法はある)

Appendix B: 改訂履歴

  • v1.0, 2015-02-26: 初稿