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 - 如果当前线程不是此对象监视器(锁)的所有者异常

     
  • 相关阅读:
    iOS resign code with App Store profile and post to AppStore
    HTTPS科普扫盲帖 对称加密 非对称加密
    appid 评价
    使用Carthage安装第三方Swift库
    AngularJS:何时应该使用Directive、Controller、Service?
    xcode7 The operation couldn't be completed.
    cocoapods pod install 安装报错 is not used in any concrete target
    xcode7 NSAppTransportSecurity
    learning uboot how to set ddr parameter in qca4531 cpu
    learning uboot enable protect console
  • 原文地址:https://www.cnblogs.com/200911/p/3903498.html
Copyright © 2011-2022 走看看