Java生产者消费者例子尝试

来源:互联网 发布:linux 通配符 编辑:程序博客网 时间:2024/05/16 00:31

前言

对于多线程、并发这些是我心中的痛,接触java以来一直没有这样的环境给我接触这些东西,这次主动出击,将它列在自己业余的学习计划中,好好的琢磨琢磨,该模仿的模仿,该改写的改写,该总结的总结~

正文

个人感觉在初次学习某个东西的时候碰到的那些问题才是最有价值的,所以我下面打算写生产者消费者的例子的时候,一步一步去改代码,发现问题所在,并总结

网上一搜,很多例子,但是关键就是Object提供的几个函数如何合理的使用。notifyAll与wait

很羞愧,这几个方法我只知道它的名字,所以我在这方面是小白,刚开始写肯定会出现很多问题,没关系,只要有逻辑,知道需要实现什么,代码写出来只是时间问题,无外乎多了些代码的调试

生产者代码

public class Producer implements Runnable{    private ProduceStatck ps;    private String producterName;    public Producer(ProduceStatck ps, String name) {        this.ps = ps;        this.producterName = name;    }    @Override    public void run() {        while(true) {            System.out.println("生产者开始了");            if(ps.index >= 9) {                try {                    System.out.println("仓库满了" + producterName + "停止生产");                    this.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            ps.produces[++ps.index] = "产品 " + ps.index + " by " + producterName ;            this.notifyAll();        }    }}

消费者代码

public class Consumer implements Runnable{    private ProduceStatck ps;    private String consumerName;    public Consumer(ProduceStatck ps, String name) {        this.ps = ps;        this.consumerName = name;    }    @Override    public void run() {        while(true) {            System.out.println("消费者开始了");            if(ps.index < 0) {                //System.out.println(ps.index+"goodman");                try {                    System.out.println("仓空了" + consumerName + "停止获取");                    this.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            String productName = ps.produces[ps.index--];            System.out.println(consumerName + "获取了产品" + productName);            this.notifyAll();        }    }}

产品代码

public class ProduceStatck {    public String[] produces = new String[10];    public Integer index = -1;}

main主函数

public class TestMain {    public static void main(String[] args) {        ProduceStatck ps = new ProduceStatck();        Thread productThread = new Thread(new Producer(ps,"生产者1"));        Thread consumerThread = new Thread(new Consumer(ps, "消费者1"));        productThread.start();        consumerThread.start();    }}

上面写好的是逻辑,运行的时候出现了下面的问题

问题一、java.lang.IllegalMonitorStateException
《IllegalMonitorStateException异常原因及解决办法 》这篇文章可以解决我现在的问题
明白为什么以后,我对生产者和消费者的类的方法做了如下的分析以及修改

分析列表
1、调用notifyAll和wait的对象必须是某个对象锁。
可以是在方法前面加个synchronized,它的对象锁就是这个类的对象(等价于synchronized(this){}同步代码块),如下代码

public synchronized void run()

也可以加在方法中的,synchronized代码块,需要指明一个对象锁,代码如下

public void run() {        synchronized (consumerName) {        }}

上面两点都要要求调用notifyAll和wait的对象是对象锁

2、上面贴出来的代码,我用的是this,所以,Consumer的run方法中让Consumer对象作为他的对象锁,Producer的run方法,我让Producer对象作为它的锁

实现的话,我只要在各自的run方法前面加上synchronized关键字

修改代码如下

Consumerpublic synchronized void run()Producerpublic synchronized void run()

再次执行,代码是不报错了,隐藏的错误可真不少

问题二、在Producer的run在执行期间,Consumer的run也在执行

我在Producer类的run中加入了一个sleep,如下

public synchronized void run() {        while(true) {            System.out.println("生产者开始了");            if(ps.index >= 9) {                try {                    System.out.println("仓库满了" + producterName + "停止生产");                    this.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            ps.produces[++ps.index] = "产品 " + ps.index + " by " + producterName ;            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            this.notifyAll();        }}

按照我的期望,Producer的run方法用synchronized锁住了,在里面执行sleep,不会释放锁,那么Consumer的run方法就不能执行,结果,在Producer的run在运行期间,Consumer的run也在执行

问题三、Producer执行wait以后,Consumer中的notifyAll,不会唤醒Producer执行的线程,从而去继续执行wait后面的代码

分析列表
1、Producer与Consumer 使用synchronized无效,查资料以后,知道需要同一个对象锁才行,而他们各自的对象锁分别是Producer对象与Consumer 对象
2、notifyAll不管用,我看了一下线程的几种状态《thread的六种状态》,Producer使用wait进入线程状态WAITING,需要用同一个锁的notifyAll来唤醒,唤醒以后,等Consumer的run方法执行完,释放锁以后,Producer的run有机会再次获得锁,执行wait后面的代码。这里Producer对象wait需要Producer.notifyAll才管用,想让Consumer .notifyAll唤醒,是不行的。想让两者互相唤醒就使用同一个对象锁,这里,两个类的共享对象是ProduceStatck,我就用它来当对象锁

上面知道问题所在以后,我对代码做了修改
修改代码
生产者代码

public void run() {        while(true) {            synchronized (ps) {                if(ps.index >= 100) {                    try {                        System.out.println("仓库满了" + producterName + "停止生产");                        ps.wait();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }                ps.produces[++ps.index] = "产品 " + ps.index + " by " + producterName ;                ps.notifyAll();            }        }}

消费者代码

public synchronized void run() {        while(true) {            synchronized (ps) {                if(ps.index < 0) {                    //System.out.println(ps.index+"goodman");                    try {                        System.out.println("仓空了" + consumerName + "停止获取");                        ps.wait();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }                String productName = ps.produces[ps.index--];                System.out.println(consumerName + "获取了产品 :" + productName);                ps.notifyAll();            }        }}

上面修改后的代码,看样子是没什么问题了

阶段性总结:
1、notifyAll,wait需要在synchronized中使用, wait进入等待状态,只有wait调用者对象,才能执行notifyAll去唤醒
2、两段代码加入了synchronized,他们对象锁不同,那么线程可以随时访问各自的方法
3、notifyAll 某一个等待的线程,不是立即就获得锁的,需要执行notifyAll那个方法释放了锁,让jvm调度,才有机会再次获得锁,执行未完成的代码
4、sleep函数不会释放锁,wait是会释放锁

上面是一个生产者和一个消费者,我觉得不够刺激,我打算来点刺激点的,我10个生产者和10个消费者一起来

生产者的代码和消费者的代码我不打算改变了,我只改变main主函数的代码

main代码如下

public class TestMain {    public static void main(String[] args) {        ProduceStatck ps = new ProduceStatck();        for(int index = 0; index < 10; index++) {            Thread productThread = new Thread(new Producer(ps,"生产者"+(index+1)));            productThread.start();        }        for(int index1 = 0; index1 < 10; index1++) {            Thread consumerThread = new Thread(new Consumer(ps, "消费者"+(index1+1)));            consumerThread.start();        }    }}

在执行的时候我先分析一下运行的状态

代码分析
1、两个for循环将20个线程状态从NEW变到RUNNABLE,执行完以后等待jvm调度
2、这20个线程的对象锁我控制成一样的,这样,当一个线程获得锁以后,等执行完,释放锁,在由其他线程争抢
3、在仓库没满之前,10个生产者都有机会去生产产品。释放锁以后有2个可能,如果满以后,线程状态变成WAITING,等待消费者10个线程的某一个线程执行。或者生产一定数量以后,由jvm去调度消费者的某一个线程获取产品
4、10个消费者线程,都有机会去获取产品,可能等仓库空了,线程状态变成WAITING,也可能获取到某一个数量的时候,由jvm调度生产者线程去执行生产
5、就这样循环下去…

貌似很完美,但是我不太自信,执行看看。

在意料之内,果然报错了,数组越界,这是什么原因?我在Producer的run中加了一行调试代码

public void run() {        while(true) {            synchronized (ps) {                if(ps.index >= 9) {                    try {                        System.out.println("仓库满了" + producterName + "停止生产");                        ps.wait();                        System.out.println(producterName + " 恢复,它的index:" + ps.index);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }                ps.produces[++ps.index] = "产品 " + ps.index + " by " + producterName ;                ps.notifyAll();            }        }}

原来,我上面想的太单纯了,生产者某一个线程抢到锁以后,执行加入产品,然后释放锁,由其它线程抢,可能是消费者线程抢到,也可能是生产者线程抢到,如果一直是生产者线程抢到,那么仓库满以后,最后加入产品的那个线程释放锁,这时候,我一厢情愿认为是消费者线程抢到锁,然后执行获取产品。还有一种可能就是还是生产者线程抢到,执行wait,该线程状态变成WAITING,释放锁,接着还是生产者其他线程抢到,等所有线程线程都变成WAITING以后(保守假设),由消费者线程某一个线程得到锁,消费者得到产品,使用notifyAll,唤醒生产者所有WAITING状态的线程。这下热闹了,这个消费者线程释放锁的那时候,这些生产者线程又来抢锁了,悲剧的是,还真被它们抢到了,抢到了就恢复线程执行wait后面的代码呗。index 被消费者取出一个产品,变成8,然后加入一个产品变成9,释放锁,又被生产者线程抢到锁,继续恢复其他WAITING线程,继续加入产品,这时候index变成10 ,程序爆红了,数组越界

简单来说生产者的run里面使用wait只是对于一个生产者线程来说,不能保证,仓库满了,所有生产者线程都一起进入WAITING状态,而是每个线程都要执行一遍wait,最坏的结果是所有生产者线程都wait,然后notifyAll恢复的时候,都没有在检查ps.index > 9了,直接执行下面代码

我尝试把notifyAll改成notify,单个线程通知,但是这样也会出问题,通知一个生产者恢复以后,生产者run代码有个notify,这个notify如果再次通知生产者其他线程恢复,还是有数组越界。

放放,放放…

参考资料

《Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition》

0 0
原创粉丝点击