原创

第七篇 : 生产者消费者案例-虚假唤醒


案例

以下是一个案例,有一个店员,负责进货和卖货。进货生产,卖货消费。 当商品超过10件,生产等待,消费继续,当少于0件,消费等待,消费继续。

一、演示的代码

package com.gf.demo;

/**
 * 生产者消费者案例,演示虚假唤醒问题
 */
public class TestProductorAndConsumer {

    public static void main(String args[]){
        Clerk clerk = new Clerk();

        Productor pro = new Productor(clerk);
        Consumer cos = new Consumer(clerk);

        new Thread(pro , "生产者A").start();
        new Thread(cos , "消费者B").start();

    }

}

/**
 * 店员
 */
class Clerk {
    private int product = 0;

    /**
     * 进货
     */
    public synchronized void get() {
        if (5 <= product) {
            System.out.println("产品已满 !");
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + ++product);
        }
    }

    /**
     * 卖货
     */
    public synchronized void sale() {
        if (0 >= product) {
            System.out.println("缺货 !");
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + --product);
        }
    }


}

/**
 * 生成者
 */
class Productor implements Runnable {

    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0 ; i < 10 ; i++) {
            clerk.get();
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable {

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0 ; i<10 ; i++) {
            clerk.sale();
        }
    }
}

我们不加等待唤醒机制 ,演示结果如下,生产和消费过快,不等待彼此。

我们加入等待唤醒机制:

/**
 * 店员
 */
class Clerk {
    private int product = 0;

    /**
     * 进货
     */
    public synchronized void get() {
        if (5 <= product) {
            System.out.println("产品已满 !");
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + ++product);
            //唤醒
            this.notifyAll();
        }
    }

    /**
     * 卖货
     */
    public synchronized void sale() {
        if (0 >= product) {
            System.out.println("缺货 !");
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + --product);
            //唤醒
            this.notifyAll();
        }
    }


}

结果:

很和谐 “没问题”。

但如果我们让 生产慢于消费 如下:

/**
 * 店员
 */
class Clerk {
    private int product = 0;

    /**
     * 进货
     */
    public synchronized void get() {
        if (1 <= product) {
            System.out.println("产品已满 !");
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + ++product);
            //唤醒
            this.notifyAll();
        }
    }

    /**
     * 卖货
     */
    public synchronized void sale() {
        if (0 >= product) {
            System.out.println("缺货 !");
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + --product);
            //唤醒
            this.notifyAll();
        }
    }


}

/**
 * 生成者
 */
class Productor implements Runnable {

    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0 ; i <10 ; i++) {
            try {
                //休息200毫秒,让生产慢于消费
                Thread.sleep( 200 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.get();
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable {

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0 ; i<10 ; i++) {

            clerk.sale();
        }
    }
}

结果:

程序最后没有结束,那么说明我们写还是有问题。

原因:这是因为生成者速度较慢,消费最后一次wait()被 notify 唤醒后,不会在执行else中的方法,那么 product数量没有变成0,导致生成者中的进入wait() ,之后就再也没有人唤醒他,这样程序就直接结束不了。

解决方法:掉 else

/**
 * 店员
 */
class Clerk {
    private int product = 0;

    /**
     * 进货
     */
    public synchronized void get() {
        if (1 <= product) {
            System.out.println( "产品已满 !" );
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println( Thread.currentThread().getName() + " : " + ++product );
        //唤醒
        this.notifyAll();

    }

    /**
     * 卖货
     */
    public synchronized void sale() {
        if (0 >= product) {
            System.out.println( "缺货 !" );
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println( Thread.currentThread().getName() + " : " + --product );
        //唤醒
        this.notifyAll();
    }

}

二、虚假唤醒

上面的案例是单个生产者单个消费者,若我们使用两个生产者,两个消费者,如下:

package com.gf.demo;

/**
 * 生产者消费者案例,演示虚假唤醒问题
 */
public class TestProductorAndConsumer {

    public static void main(String args[]) {
        Clerk clerk = new Clerk();

        Productor pro = new Productor( clerk );
        Consumer cos = new Consumer( clerk );

        new Thread( pro, "生产者A" ).start();
        new Thread( cos, "消费者B" ).start();

        new Thread( pro, "生产者C" ).start();
        new Thread( cos, "消费者D" ).start();


    }

}

/**
 * 店员
 */
class Clerk {
    private int product = 0;

    /**
     * 进货
     */
    public synchronized void get() {
        if (1 <= product) {
            System.out.println( "产品已满 !" );
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println( Thread.currentThread().getName() + " : " + ++product );
        //唤醒
        this.notifyAll();

    }

    /**
     * 卖货
     */
    public synchronized void sale() {
        if (0 >= product) {
            System.out.println( "缺货 !" );
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println( Thread.currentThread().getName() + " : " + --product );
        //唤醒
        this.notifyAll();

    }


}

/**
 * 生成者
 */
class Productor implements Runnable {

    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                //休息200毫秒,让生产慢于消费
                Thread.sleep( 200 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.get();
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable {

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {

            clerk.sale();
        }
    }
}

运行结果:

出现了负数,代码肯定有问题。
这种现象叫做虚假唤醒,jdk文档写的很清楚,如下:

所以我们按照文档告诉我们,把 if 改为while就可以解决问题 ,循环时,在判断一次。

/**
 * 店员
 */
class Clerk {
    private int product = 0;

    /**
     * 进货
     */
    public synchronized void get() {
        while (1 <= product) {
            System.out.println( "产品已满 !" );
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println( Thread.currentThread().getName() + " : " + ++product );
        //唤醒
        this.notifyAll();

    }

    /**
     * 卖货
     */
    public synchronized void sale() {
        while (0 >= product) {
            System.out.println( "缺货 !" );
            try {
                //等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println( Thread.currentThread().getName() + " : " + --product );
        //唤醒
        this.notifyAll();

    }


}

运行结果:

问题解决了 !

juc
  • 作者:程序员果果
  • 发表时间:2018-11-04 09:26
  • 版权声明:自由转载-非商用-非衍生-保持署名 (创意共享4.0许可证)
  • 公众号转载:请在文末添加作者公众号二维码
  • 评论