『Java言語で学ぶデザインパターン(マルチスレッド編)』まとめ(その１)

<img width="300" alt="javathread.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/165994/9d302410-0471-96c1-f097-d46649d17209.jpeg">

『Java言語で学ぶデザインパターン(マルチスレッド編)』を読んだので要点を記録していく。

#Java言語のスレッド
##スレッドの起動
- 方法１　java.lang.Threadクラスを使う。

あたらしく起動されるスレッドの動作は**Thread**クラスの**run**メソッドに記述する。Threadクラスの**start**メソッドを呼び出すと新しいスレッドが起動する。startメソッドを呼び出すと、新しいスレッドの起動、runメソッドの呼び出し、が行われる。

```java
public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 100; i++) {
            System.out.print("1");
        }
    }

    public static class MyThread extends Thread {
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.print("2");
            }
        }
    }
}
```

1回目

```text:実行結果
11111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222211111111111111111111111111111111111111111111111111111111111111111111222222
```

2回目

```:実行結果
11111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222211111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
```

- 方法2　Runnbaleインターフェースを使う

**Runnable**インターフェースを実装したクラスのインスタンスを使ってスレッドを起動する。Runnableインターフェースは次のように宣言されている。

```java
public interface Runnable {
    public abstract void run();
}
```

Runnbaleインターフェースを実装したクラスを作り、そのインスタンスをThreadのコンストラクタに渡し、startメソッドを呼び出す。

```java
public class Main {
    public static void main(String[] args) {
        Runnable myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.print("1");
        }
    }

    public static class MyThread implements Runnable {
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.print("2");
            }
        }
    }
}
```

##スレッドの一時停止
Thread.sleepを使用する。**sleep**メソッドの呼び出しは、try-catchで囲む。sleepメソッドは**InterruptedException**例外を投げる。

```java
public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.print("1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
}
```

##スレッドの排他制御
スレッドAとスレッドBが競争(レース)して引き起こされる、期待に反するスレッドの挙動を**データ・レース**(data race)と呼ぶ。data traceを防ぐためには、交通整理が必要で、これを一般的に**排他制御**と呼ぶ。Javaでは、スレッドの排他制御を行うときに**synchronized**というキーワードを使う。メソッドにsynchronizedというキーワードをつけて宣言すると、そのメソッドは一つのスレッドで動作するようになる。このようなメソッドを**synchronizedメソッド**、**同期メソッド**と呼ぶ。

```java
public class Bank {
    private int money;
    private String name;

    public Bank(String name, int money) {
        this.name = name;
        this.money = money;
    }

    // 預金する
    public synchronized void deposit(int m) { // 同期メソッド
        money += m;
    }

    // 引き出す
    public synchronized boolean withdraw(int m) { // 同期メソッド
        if (money >= m) {
            money -= m;
            return true;
        } else {
            return false;
        }
    }

    public String getName() {
        return name;
    }
}
```

スレッドが一つでも**ロック**をとっている場合、他のスレッドは入ることができない。synchronizedメソッドを実行していたスレッドがそのメソッドの実行を終えると、ロックが**解放(release)**される。ロックが解放されると、いままでロックが取られていたために入れなかったスレッドのうち、どれか一つがロックを取ることができる。

ロックはインスタンスごとに存在する。あるインスタンスのsynchronizedメソッドを実行しているからといって、別のインスタンスのsynchronizedメソッドが実行できなくなるわけではない。

現在のスレッドが、あるオブジェクトのロックをとっているかどうかは、Thread.holdsLockメソッドで調べることができる。

メソッド全体ではなく、メソッドの一部を一つのスレッドで動作させたい場合には、**synchronizedブロック**を使う。

```java
synchronized (式) {
    ...
}
```

###synchronizedなインスタンスメソッドとsynchronizedブロック

以下は等価

```java
synchronized void method() {
    ...
}

void method() {
    synchronized (this) {
        ...
    }
}
```

###synchronizedなクラスメソッドとsynchronizedブロック

以下は等価

```java
class Something {
    static synchronized void method() {
        ....
    }
}

class Something {
    static void method() {
        synchronized (Something.class) {
            ....
        }
    }
}
```

##スレッドの協調
全てのインスタンスは**ウェイトセット**というものを持ている。ウェイトセットはそのインスタンスの**wait**メソッドを実行し、動作を停止しているスレッドの集合。インスタンスごとに用意されているスレッドの待合室のようなもの。スレッドは、waitメソッドを実行すると、動作を一時停止し、ウェイトセットという待合室に入る。スレッドは以下のどれかが発生するまでそのウェイトセットの中で待つ。

- 他のスレッドからnotifyメソッドによって起こされる
- 他のスレッドからnotifyAllメソッドによって起こされる
- 他のスレッドからinterruptメソッドによって起こされる
- waitメソッドがタイムアウトする

例えば、
obj.wait();
という文を実行したとき、現在のスレッドを一時停止し、インスタンスobjのウェイトセットに入る。このことをスレッドがobjの上でwaitしているという。waitメソッドを実行するためには、スレッドがロックを持っていなければならない。

ウエイトセットは仮想的な概念。インスタンス上でウェイトしているスレッドの一覧を得るメソッドはない。

(本書にこの概念をわかりやすく描いた図があるのでそちらを参照するとよい)

**notify**メソッドを使うと、ウェイトセットにいるスレッドの一つをウェイトセットから出す。
例えば、
obj.notify();
という文をあるスレッドが実行したとする。すると、objのウェイトセットの中にいるスレッドから一つのスレッドが選ばれ、そのスレッドが起こされる。起こされたスレッドはウェイトセットから外へ出る。notifyメソッドを実行するためには、スレッドがロックを持っていなければならない。**notifyで起こされたスレッドは、notifyを行った瞬間に実行を再開するわけではない。**なぜなら、notifyを行った瞬間は、notifyしたスレッドがロックを持っているため。**notifyメソッドを実行したとき、ウェイトセットで待っているスレッドが複数存在した場合、どのスレッドが選ばれるかは、仕様では定められていない。**

notifyAllメソッドを使うと、ウェイトセットにいる全てのスレッドをウェイトセットから出す。notifyAllメソッドはwaitメソッド、norifyメソッドと同様に、呼び出すインスタンスのロックを持ったスレッドからしか呼び出せない。notify同様、出たスレッドがすぐに再開されるわけではない。notifyAllを実行したスレッドがロックを解放してから、その中の幸運な一つのスレッドだけが実行を再開できる。

ロックを持っていないスレッドがwait, notify, notifyAllを呼び出すと例外**java.lang.IllegalMonitorStateException**が投げられる。

##スレッドの状態遷移
スレッドは以下の状態を持つ。

- NEW：起動していないスレッドの状態
- RUNNABLE：Java仮想マシンで実行されているスレッドの状態
- TERMINATED：終了したスレッドの状態
- WAITING：ほかのスレッドが特定のアクションを実行するのを無期限に待機しているスレッドの状態 
- TIMED_WAITING：指定された待機時間、ほかのスレッドがアクションを実行するのを待機しているスレッドの状態
- BLOCKED：ブロックされ、モニターロックを待機しているスレッドの状態 

これら状態はThreadクラスのgetStateメソッドで得ることができる。

---

ソースコードは筆者のHPからDL可能。
http://www.hyuki.com