JAVA多线程案例之数字加减(生产者消费者模式)中遇到的问题

5 篇文章 0 订阅
3 篇文章 0 订阅

直接看一段代码,经典的生产者消费者模式,用多线程来实现数字的加减:

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三种结果,然而真实的运行结果却不是这样:

[加法操作 - 加法线程 - 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的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者开始小号数据。所以记住,永远在while循环而不是if语句中使用wait!我会推荐阅读《Effective Java》,这是关于如何正确使用wait和notify的最好的参考资料。

那么我们之前做的while和if...else...语句的操作其实是一样的,就是保护核心逻辑代码不被错误的唤醒执行,碰巧解决了这个问题。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值