zoukankan      html  css  js  c++  java
  • java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

     *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型
     *对于此模型,应该明确以下几点:
     *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产。
     *2.消费者仅仅在有产品的时候才能消费,仓空则等待。
     *3.当消费者发现仓储没有产品可消费的时候,会唤醒等待生产者生产。
     *4.生产者在生产出可以消费的产品的时候,应该通知等待的消费者去消费。

    下面先介绍个简单的生产者消费者例子:本例只适用于两个线程,一个线程生产,一个线程负责消费。

    生产一个资源,就得消费一个资源。

    代码如下:

    public class ProduceOneCusumer {
        
        /**
         * @param args
         */
        public static void main(String[] args) {
            Resource r = new Resource();
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
            Thread t1 = new Thread(pro);//生产者线程
            Thread t2 = new Thread(con);//消费者线程
            t1.start();
            t2.start();
    
        }
    
    }
    /**
     * 仓储类,仓储原料
     * @author Administrator
     *
     */
    class Resource{
        private String name;//原料名称
        private int count = 1;//生产数量
        private boolean flag = false;
        
        public synchronized void set(String name){//t1
            if(flag){
                try {
                    this.wait();//仓库满了,生产者等待,等待消费者消费
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name+"------"+count++;
            System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
            this.flag = true;
            this.notify();//唤醒消费者线程进行消费
            
        }
        public synchronized void out(){//t2,t3
            if(!flag){//false
                try {
                    this.wait();//仓库为空不能消费,消费者线程等待,等待生产者生产
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"......消费者......."+this.name);
            this.flag = false;
            this.notify();//唤醒生产者
        }
    }
    
    /**
     * 生产者类
     * @author Administrator
     *
     */
    class Producer implements Runnable{
        private Resource r;
        public Producer(Resource r){//把原料放到生产者里面生产,当生产者一创建生产对象,就初始化原料
            this.r = r;
        }
        @Override
        public void run() {
            while(true){//不断的生产
                r.set("烤鸭");//生产烤鸭
            }
        }
    }
    
    class Consumer implements Runnable{
        private Resource r;
        public Consumer(Resource r){
            this.r = r;
        }
    
        @Override
        public void run() {
            while(true){
                r.out();//消费者消费
            }
        }
        
    }

    输出结果:

    Thread-1......消费者.......烤鸭------21688
    Thread-0..生产者..烤鸭------21689
    Thread-1......消费者.......烤鸭------21689
    Thread-0..生产者..烤鸭------21690
    Thread-1......消费者.......烤鸭------21690
    Thread-0..生产者..烤鸭------21691
    Thread-1......消费者.......烤鸭------21691
    Thread-0..生产者..烤鸭------21692
    Thread-1......消费者.......烤鸭------21692

    从结果可以得知,生产者线程生产一只烤鸭,消费者就消费一只烤鸭。两个线程交替等待唤醒。

    下面我们把上面代码稍微改动下:再增加两个线程,变为两个生产者线程分别为t0,t1,两个消费者线程,分比为t3,t4.改动Main方法,其它不变。

    代码如下:

    public class ProduceOneCusumer {
        
        /**
         * @param args
         */
        public static void main(String[] args) {
            Resource r = new Resource();
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
            Thread t0 = new Thread(pro);//生产者线程
            Thread t1 = new Thread(pro);//生产者线程
            Thread t2 = new Thread(con);//消费者线程
            Thread t3 = new Thread(con);//消费者线程
            t0.start();
            t1.start();
            t2.start();
            t3.start();
    
        }
    
    }

    则经过测试代码运行结果:会发现:

                  第一种情况:生产者线程一直在生产,仓库满了也不消费;

                                            第二种情况:生产了一个 ,消费了多次,也就是说库存里面没有烤鸭了还在消费。

    结果如下:

    第一种情况:

    Thread-0..生产者..烤鸭------28348
    Thread-1..生产者..烤鸭------28349
    Thread-0..生产者..烤鸭------28350
    Thread-1..生产者..烤鸭------28351
    Thread-0..生产者..烤鸭------28352
    Thread-1..生产者..烤鸭------28353
    Thread-0..生产者..烤鸭------28354
    Thread-1..生产者..烤鸭------28355
    Thread-0..生产者..烤鸭------28356
    Thread-1..生产者..烤鸭------28357
    Thread-0..生产者..烤鸭------28358
    Thread-2......消费者.......烤鸭------28358

    第二种情况:

    Thread-0..生产者..烤鸭------29432
    Thread-2......消费者.......烤鸭------29432
    Thread-3......消费者.......烤鸭------29432
    Thread-2......消费者.......烤鸭------29432
    Thread-3......消费者.......烤鸭------29432
    Thread-2......消费者.......烤鸭------29432

    结论:多个线程会出现问题,思考原因?

    这里分析代码解释第一种情况,第二种情况和第一种情况类试。

    代码的执行过程分析:

         假设有两个生产线程为t0,t1;两个消费线程为t1,t2。假设开始生产者t0得到了CPU的执行权,进入生产者run方法,拿到this锁,进入set方法,初始值flag = false;所以不走if中的代码,走else中的代码,则生产者生产了一只烤鸭(打印出"Thread-0..生产者..烤鸭1"),thread-0执行完剩余的代码,释放this锁。

          这个时候,因为thread-0是执行完run方法中的set方法中的所有代码,释放锁,并没有执行wait方法,那么线程0还可能抢到CUP资源,拿到 锁,再次进入Run方法中的set方法,进入判断if(flag=true),所以线程0(Thread-0)等待。

          这时活跃的线程还有t1,t2,t3;一个生产线程,两个消费线程.这个时候还有可能t1抢到CPU的执行权, t1拿到this锁,进入set方法,if(true),那么执行wait,这个时候t1(Thread-1)也等待。

         这个时候:还有两个线程活着,t2,t3。假设t2抢到CPU的执行权,在消费者run方法里面运行,执行out函数,拿到锁进入out方法,if 判断,由于上面flag=true;所以不执行if代码,执行else代码,则消费者消费一只烤鸭(打印Thread-2....消费者.....烤鸭1),紧跟着
     执行t2执行剩余的代码,flag=false;notify()。

         这个时候等待线程池中有2个线程处于冻结状态,分别为t0,t1。当执行notify()方法的时候,“就可以唤醒同锁中等待的线程中的一个”。可能是t1,也可能是t2。假设t1被唤醒,这时活跃的线程还包括t2,t3,t0.假设还是t2抢到CPU执行权,t2进入out,由于flag被置为flase,那么if判断,这个时候t2执行wait方法,等待。这个时候剩余t0,t3是活着的,假设t3抢到执行权,拿到锁进入out方法,判断,t3也等待。这个时候还有一个线程处于活跃状态,那就是t0.

        关键就是这个地方:四个线程t1,t2,t3都执行了wait方法进入等待状态,t0那会儿执行wait,刚被notify唤醒,唤醒之后,直接执行 else里面的代码,这时候生产者生产了一只烤鸭(打印出"Thread-0..生产者..烤鸭2"),flag=true,这个时候执行notify,最悲剧的是有可能唤醒了t1,和t0一样的生产者。这个时候两个 生产者线程活着。为t0,t1,可能t0抢到CPU执行权,进入set,if判断,执行wait,t0等待,这个时候活过来的只剩t1,由于t1是从 wait中活过来的,所以不用if判断,执行else中的代码,生产者生产了一只烤鸭(打印出"Thread-1..生产者..烤鸭3")。这样flag=true,notify,又有可能唤醒t0,t2,t3中的 t1.这样可能形成一个规律就是只生产,不消费。就会导致上面的结果。

        造成的根本原因是:if只判断一次,当等待的线程醒过来以后,没有执行if判断,那么使用while循环就可以解决这个问题,等待的线程醒过来之后还会判断里面的条件。

    代码变成:把if改为while:

         

    public class ProduceOneCusumer {
        
        /**
         * @param args
         */
        public static void main(String[] args) {
            Resource r = new Resource();
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
            Thread t0 = new Thread(pro);//生产者线程
            Thread t1 = new Thread(pro);//生产者线程
            Thread t2 = new Thread(con);//消费者线程
            Thread t3 = new Thread(con);//消费者线程
            t0.start();
            t1.start();
            t2.start();
            t3.start();
    
        }
    
    }
    /**
     * 仓储类,仓储原料
     * @author Administrator
     *
     */
    class Resource{
        private String name;//原料名称
        private int count = 1;//生产数量
        private boolean flag = false;
        
        public synchronized void set(String name){//t1
            while(flag){
                try {
                    this.wait();//仓库满了,生产者等待,等待消费者消费
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name+"------"+count++;
            System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
            this.flag = true;
            this.notify();//唤醒消费者线程进行消费
            
        }
        public synchronized void out(){//t2,t3
            while(!flag){//false
                try {
                    this.wait();//仓库为空不能消费,消费者线程等待,等待生产者生产
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"......消费者......."+this.name);
            this.flag = false;
            this.notify();//唤醒生产者
        }
    }
    
    /**
     * 生产者类
     * @author Administrator
     *
     */
    class Producer implements Runnable{
        private Resource r;
        public Producer(Resource r){//把原料放到生产者里面生产,当生产者一创建生产对象,就初始化原料
            this.r = r;
        }
        @Override
        public void run() {
            while(true){//不断的生产
                r.set("烤鸭");//生产烤鸭
            }
        }
    }
    
    class Consumer implements Runnable{
        private Resource r;
        public Consumer(Resource r){
            this.r = r;
        }
    
        @Override
        public void run() {
            while(true){
                r.out();//消费者消费
            }
        }
        
    }

    测试结果:不会出现上面的那两种情况了,但可能出现死锁。

    Thread-0..生产者..烤鸭------1
    Thread-2......消费者.......烤鸭------1
    Thread-1..生产者..烤鸭------2
    Thread-2......消费者.......烤鸭------2
    Thread-0..生产者..烤鸭------3
    Thread-2......消费者.......烤鸭------3
    Thread-0..生产者..烤鸭------4
    Thread-2......消费者.......烤鸭------4
    Thread-0..生产者..烤鸭------5
    Thread-3......消费者.......烤鸭------5
    Thread-0..生产者..烤鸭------6
    Thread-2......消费者.......烤鸭------6

    死锁原因:

    当改成while循环后,由于刚上面的分析t1从等待中唤醒,t0,t2,t3等待了,由于flag=true,改成while循环后,会再次判断标志,while条件成立,则t1执行wait方法,等待。这个时候t0,t1,t2,t3都处于等待状态,没有线程唤醒,那么出现了死锁。简单的说就是四个线程都等待,没有活着的线程,程序出现死锁,不会循环生产和消费。

    那么怎么解决呢:把notify改为notifyAll方法,出现的原因是每次只能任意的唤醒一个线程,当改成notifyAll方法后可以唤醒所有等待的线程。

    最终多个线程的消费者生产者程序问题全部解决。

    总结:两个线程用if判断可以,多个线程的生产者消费者问题用while循环判断标志。在分析的过程中我们可以理解wait,notify和notifyAll的用法,三个方法都用在同一个资源当中的同一个锁中,成对出现,必须用在同步(锁)当中,否则会出现IllegalMonitorStateException - 如果当前线程不是此对象监视器(锁)的所有者异常

     
  • 相关阅读:
    对于在git上面拉代码报"error: RPC failed; curl 56 OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 10054"解决方法
    在vue项目中如何添加eslint
    vscode编辑如何保存时自动校准eslint规范
    css3动画
    JS中的深拷贝与浅拷贝
    JS中的防抖与节流
    金三银四求职季,前端面试题小梳理(HTML、CSS、JS)
    正则表达式元字符大整理
    常规正则表达式练习,一起来开心的掉发吧
    关于子元素的margin-top对父级容器无效
  • 原文地址:https://www.cnblogs.com/200911/p/3903498.html
Copyright © 2011-2022 走看看