【並列デザインパターン連載】Single Thread Excecutionパターン

業務で非同期処理を書いたときに「そのクラス、スレッドセーフですか？」と指摘を受けることが複数回ありました。
そこで、「そもそもスレッドセーフって何よ」「スレッドセーフじゃないと何が困るのよ」ということを勉強しました。本記事はそのまとめとなってます。


##参考図書
https://www.amazon.co.jp/%E5%A2%97%E8%A3%9C%E6%94%B9%E8%A8%82%E7%89%88-Java%E8%A8%80%E8%AA%9E%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E5%85%A5%E9%96%80-%E3%83%9E%E3%83%AB%E3%83%81%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E7%B7%A8-%E7%B5%90%E5%9F%8E-%E6%B5%A9/dp/4797331623

この本は思考させる系の練習問題がついてて、良い気がします。
自分もまだ読破できてませんが(笑)

以下は、この本の第1章「Single Threaded Execution」が基になっています。

##「スレッドセーフでない」とは

インスタンスまたはメソッドが、複数のスレッドから実行されたときに、期待した挙動をしなくなるような状態のこと。


####要件
・一度に一人しか通れない門が一つある。通行人が3人いて、それぞれが頻繁にこの門を通る。
・門が通行人を通るごとに、下記の3つの値を表示したい。
　-それまで門を通った人数
　-通った人の名前
　-通った人の出身地

・通行人は下記の3人。
　-名前:Alice 出身地:Alaska
　-名前:Bobby 出身地:Brazil
　-名前:Clice 出身地:Canada

####クラス設計

|名前|役割|
|--|--|
|Main|Javaのメインクラス。門を作り、人を通らせる|
|Gate|門を表すクラス。人が通る時に"それまでに通った人数""通った人の名前""通った人の出身地"を表示する|
|UserThread|人を表すクラス。Mainクラスが終了するまで門を通り続ける|

####実装
※こちら↓からDL可能です。
※筆者が作ったものではありません
https://www.hyuki.com/dp/dp2.html#download

```Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println("Testing Gate, hit CTRL+C to exit.");
        Gate gate = new Gate();
        new UserThread(gate, "Alice", "Alaska").start();
        new UserThread(gate, "Bobby", "Brazil").start();
        new UserThread(gate, "Chris", "Canada").start();
    }
}
```

```Gate.java
public class Gate {
    private int counter = 0;
    private String name = "Nobody";
    private String address = "Nowhere";
    public void pass(String name, String address) {
        this.counter++;
        this.name = name;
        this.address = address;
        check();
    }
    public String toString() {
        return "No." + counter + ": " + name + ", " + address;
    }
    private void check() {
        if (name.charAt(0) != address.charAt(0)) {
            System.out.println("***** BROKEN ***** " + toString());
        }
    }
}
```

```UserThread.java
public class UserThread extends Thread {
    private final Gate gate;
    private final String myname;
    private final String myaddress;
    public UserThread(Gate gate, String myname, String myaddress) {
        this.gate = gate;
        this.myname = myname;
        this.myaddress = myaddress;
    }
    public void run() {
        System.out.println(myname + " BEGIN");
        while (true) {
            gate.pass(myname, myaddress);
        }
    }
}
```

Gateクラスでは、checkメソッドで通過する人の**名前**と**出身地**の頭文字が異なっていた場合に`BROKEN`を標準出力しています。


####動作確認

`BROKEN`がたくさん出ました。なぜでしょう。
しかも、不思議なのは「名前と出身地の頭文字が合っている」「名前と出身地の頭文字が異なる」両方の場合に`BROKEN`が出てしまっていることです。

"複数スレッド(=各UserThreadクラス)から実行されたときに期待した挙動しない"状態なので、ここではGateクラスがスレッドセーフでない状態になっています。

```
Testing Gate, hit CTRL+C to exit.
Alice BEGIN
Bobby BEGIN
Chris BEGIN
***** BROKEN ***** No.633: Bobby, Brazil //名前と出身地の頭文字が合っている
***** BROKEN ***** No.990: Bobby, Brazil
***** BROKEN ***** No.1511: Alice, Alaska
***** BROKEN ***** No.1928: Alice, Alaska
***** BROKEN ***** No.2512: Bobby, Brazil
***** BROKEN ***** No.2750: Chris, Canada
***** BROKEN ***** No.2998: Bobby, Brazil
***** BROKEN ***** No.3198: Bobby, Brazil
***** BROKEN ***** No.3499: Bobby, Brazil
***** BROKEN ***** No.4024: Bobby, Brazil
***** BROKEN ***** No.2198: Bobby, Brazil
***** BROKEN ***** No.4391: Bobby, Brazil
***** BROKEN ***** No.5290: Alice, Alaska
***** BROKEN ***** No.5514: Chris, Alaska //名前と出身地の頭文字が異なる
***** BROKEN ***** No.4901: Alice, Alaska
```

####なぜこうなる？
各UserThreadクラス(=Alice,Bobby,Chris)はpassメソッドを実行し、Gateクラスのクラス変数である`counter`,`name`,`address`を次々と変更します。

**Aliceさんは今Gateクラス変数がどのような状態になっているかなどお構いなしにpassメソッドを実行し続けます。BobbyさんもChrisさんも然りです。**


もしかしたらタイミングによっては、次のような処理順になることもあります。

#####BROKEN出力 処理順例1（出力された名前と出身地の頭文字が異なっている場合）
* AliceさんがGateクラス変数`name`を`Alice`に変更する
* BobbyさんがGateクラス変数`address`を`Brazil`に変更する
* Gateクラスの`check`メソッドが実行される
* `name`が`Alice`,`Address`が`Brazil`なので、`name.charAt(0) != address.charAt(0)`がtrueとなる
*  `BROKEN, Alice, Brazil`が出力される

#####BROKEN出力 処理順例2（出力された名前と出身地の頭文字が合っている場合）
* AliceさんがGateクラス変数`name`を`Alice`に変更する
* BobbyさんがGateクラス変数`address`を`Brazil`に変更する
* Gateクラスの`check`メソッドが実行される
* `name`が`Alice`,`Address`が`Brazil`なので、`name.charAt(0) != address.charAt(0)`がtrueとなる
* AliceさんがGateクラス変数`address`を`Alaska`に変更する
* `BROKEN, Alice, Alaska`が出力される

つまり、各スレッド(Alice,Bobby,Chris)がGateクラスに干渉する順番によって、得られる結果が異なる状態になっています。現状、干渉する順は制御できません。

##改善方法

>Aliceさんは今Gateクラス変数がどのような状態になっているかなどお構いなしにpassメソッドを実行し続けます。BobbyさんもChrisさんも然りです。`

ココが良くないので、Gateクラスのメソッド側で「自分を使っていいのは一回に一人だけ、同時に実行しないでね」という制限をかけます。抽象的な言葉で言うと「排他制御」ですね。

具体的にはJavaの`syncronized`句を使います。
メソッドに`syncronized`句を付けることで、このような仕様が追加されます

* あるスレッドが`syncronized`メソッドを使う際、メソッドのロックを取得する
* ロックを取得されたメソッドを他スレッドが使用すると、ブロックされる
* メソッドの使用が終わるとロックが解放され、他スレッドが使用可能になる

```(排他制御後)Gate.java
public class Gate {
    private int counter = 0;
    private String name = "Nobody";
    private String address = "Nowhere";
    //syncronizedで1度に1スレッドからしか使用できなくする　　　　
    public synchronized void pass(String name, String address) {
        this.counter++;
        this.name = name;
        this.address = address;
        check();
    }
    //syncronizedで1度に1スレッドからしか使用できなくする
    public synchronized String toString() {
        return "No." + counter + ": " + name + ", " + address;
    }
    private void check() {
        if (name.charAt(0) != address.charAt(0)) {
            System.out.println("***** BROKEN ***** " + toString());
        }
    }
}
```

####動作確認

排他制御前の実行では数秒とまたずに`BROKEN`していましたが、数秒待っても`BROKEN`しなくなりました。
(何時間もやってると壊れ始めるのかもしれませんが...)

```
Testing Gate, hit CTRL+C to exit.
Alice BEGIN
Bobby BEGIN
Chris BEGIN
```

####どうして治ったの？

passメソッドを使えるのが1度に1スレッドになり、下記のような処理順が実現されるためです。

#####非BROKEN出力 処理順例
* Aliceさんがpassメソッドのロックを取得する 
* AliceさんがGateクラス変数`name`を`Alice`に変更する
* AliceさんがGateクラス変数`address`を`Alaska`に変更する
* Aliceさんがpassメソッドのロックを開放する 
* Gateクラスの`check`メソッドが実行される
* `name`が`Alice`,`address`が`Alaska`なので、`name.charAt(0) != address.charAt(0)`がfalseとなりBROKENしない
* Bobbyさんがpassメソッドのロックを取得する 
* BobbyさんがGateクラス変数`name`を`Bobby`に変更する
* BobbyさんがGateクラス変数`address`を`Brazil`に変更する
* Bobbyさんがpassメソッドのロックを開放する 
* Gateクラスの`check`メソッドが実行される
* `name`が`Bobby`,`address`が`Brazil`なので、`name.charAt(0) != address.charAt(0)`がfalseとなりBROKENしない（以下略）

##まとめ
* 「スレッドセーフでない」とは、メソッドやインスタンスが複数スレッドから使用された時に意図しない挙動をしてしまう状態のこと。
* 原因は、メソッドやインスタンスが複数スレッドで使用される際の使用順が制御されていないため。
* 防ぐには、排他制御が必要。
* 排他制御の一つとして、メソッドに「syncronized」句をつける方法がある。