JAVAマルチスレッド事例のデジタル加減(生産者消費者モデル)で発生した問題


コードを直接見て、古典的な生産者消費者モデルを見て、マルチスレッドで数字の加減を実現します.
package test;

/**
 * Created by ZhuHao on 2018/10/14
 */

class Resource{
    private int num = 0;
    private boolean flag = true;

    public synchronized void add() throws Exception{
        if(this.flag == false){
            this.wait();
        }
        Thread.sleep(10);
        this.num++;
        System.out.println("[     - "+ Thread.currentThread().getName() + "] num = " + this.num);
        this.flag = false;
        this.notifyAll();
    }
    public synchronized void sub() throws Exception{
        if(this.flag == true){
            this.wait();
        }
        Thread.sleep(20);
        this.num--;
        System.out.println("[     - "+ Thread.currentThread().getName() + "] num = " + this.num);
        this.flag = true;
        this.notifyAll();
    }
}
class AddThread implements Runnable{
    private Resource resource;
    public AddThread(Resource resource){
        this.resource = resource;
    }

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            try {
                this.resource.add();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
class SubThread implements Runnable{
    private Resource resource;
    public SubThread(Resource resource){
        this.resource = resource;
    }

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            try {
                this.resource.sub();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        Resource res = new Resource();
        AddThread at = new AddThread(res);
        SubThread st = new SubThread(res);
        new Thread(at,"     - A").start();
        new Thread(at,"     - B").start();
        new Thread(st,"     - X").start();
        new Thread(st,"     - Y").start();
    }
}

numは1、0、-1の3つの結果しか現れないことを期待していますが、実際の実行結果はそうではありません.
[     -      - A] num = 1
[     -      - Y] num = 0
[     -      - B] num = 1
[     -      - X] num = 0
[     -      - Y] num = -1
[     -      - A] num = 0
[     -      - Y] num = -1
[     -      - X] num = -2
[     -      - B] num = -1
[     -      - X] num = -2
[     -      - Y] num = -3
[     -      - A] num = -2
[     -      - Y] num = -3
[     -      - B] num = -2
[     -      - X] num = -3
[     -      - Y] num = -4
[     -      - A] num = -3
[     -      - Y] num = -4
[     -      - X] num = -5
[     -      - B] num = -4
[     -      - X] num = -5
[     -      - Y] num = -6
[     -      - A] num = -5
[     -      - Y] num = -6
[     -      - X] num = -7
[     -      - B] num = -6
[     -      - X] num = -7
[     -      - Y] num = -8
[     -      - A] num = -7
[     -      - X] num = -8
[     -      - B] num = -7
[     -      - X] num = -8
[     -      - A] num = -7
[     -      - B] num = -6
[     -      - A] num = -5
[     -      - B] num = -4
[     -      - A] num = -3
[     -      - B] num = -2
[     -      - A] num = -1
[     -      - B] num = 0

いろいろ考えてみたところif判断の問題で、マルチスレッドの場合は条件が満たされていないようでスレッドが呼び出される可能性もあるので、ifをwhileに変えたりifを使ったり...else...コードブロックを包むと正常に結果が得られます.
package test;

/**
 * Created by ZhuHao on 2018/10/14
 */

class Resource{
    private int num = 0;
    private boolean flag = true;

    public synchronized void add() throws Exception{
        while(this.flag == false){
            this.wait();
        }
        Thread.sleep(10);
        this.num++;
        System.out.println("[     - "+ Thread.currentThread().getName() + "] num = " + this.num);
        this.flag = false;
        this.notifyAll();
    }
    public synchronized void sub() throws Exception{
        while(this.flag == true){
            this.wait();
        }
        Thread.sleep(20);
        this.num--;
        System.out.println("[     - "+ Thread.currentThread().getName() + "] num = " + this.num);
        this.flag = true;
        this.notifyAll();
    }
}
class AddThread implements Runnable{
    private Resource resource;
    public AddThread(Resource resource){
        this.resource = resource;
    }

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            try {
                this.resource.add();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
class SubThread implements Runnable{
    private Resource resource;
    public SubThread(Resource resource){
        this.resource = resource;
    }

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            try {
                this.resource.sub();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        Resource res = new Resource();
        AddThread at = new AddThread(res);
        SubThread st = new SubThread(res);
        new Thread(at,"     - A").start();
        new Thread(at,"     - B").start();
        new Thread(st,"     - X").start();
        new Thread(st,"     - Y").start();
    }
}

実行後の結果は、次のようになります.
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0
[     -      - A] num = 1
[     -      - X] num = 0
[     -      - B] num = 1
[     -      - Y] num = 0

後で考証してみると、雲が貼られていました.
常にループ(loop)でwaitとnotifyを呼び出し、If文ではありません.
waitはsynchronizedの背景の下でマルチスレッドで共有されているオブジェクトと永遠に呼び出されるべきであることを知っています.次の覚えておくべき問題は、if文ではなくwhileループでwaitを呼び出すべきだということです.スレッドはいくつかの条件で待機しているため、私たちの例では、「バッファキューがいっぱいであれば、生産者スレッドは待機すべきだ」と直感的にif文を書くことができます.しかしif文には微妙な問題があり、条件が満たされていなくてもスレッドが誤って起動する可能性があります.したがって、スレッドが起動された後にwhileループを再使用して起動条件が満たされているかどうかを確認しないと、バッファがいっぱいになったときに生産者がデータを生成し続けたり、バッファが空になったときに消費者が小さいサイズのデータを開始したりするなど、プログラムがエラーになる可能性があります.だから、if文ではなくwhileループでwaitを使用することを覚えておいてください.「Effective Java」をお勧めします.waitとnotifyを正しく使用する方法についての最高の参考資料です.
前にやったwhileとifは...else...文の操作は実は同じで、コアロジックコードを誤った起動によって実行されないように保護し、たまたまこの問題を解決した.