zoukankan      html  css  js  c++  java
  • 2.5多线程(Java学习笔记)生产者消费者模式

    一、什么是生产者消费者模式

    生产者生产数据存放在缓冲区,消费者从缓冲区拿出数据处理。

    可能大家会问这样有何好处?

    1.解耦

    由于有了缓冲区,生产者和消费者之间不直接依赖,耦合度降低,便于程序拓展和维护。

    如果没有缓冲区消费者与生产者是直连的,改动生产者可能对消费者造成影响。

    2.并发处理,提升效率

    消费者和生产者分离后,两者不形成依赖可以独立运行,提高了效率。

    如果是消费者和生产者是直接接触没有缓冲区,假如消费者消费太慢,生产者也只能等待消费者消费完。这样浪费资源。

    中间多个缓冲区后,消费者消费慢生产者可以先把生产的东西放在缓冲区,然后等待消费者慢慢消费。

    举个例子,就像寄信我们是寄信人可以看做生产者(生产了信)。邮递员看做消费者(处理我们的信),缓冲区就看做是邮筒。

    没有缓冲区的话我们就要直接和快递员打交道,假如我们做出什么更改邮递员也需要同样做出更改满足我们的需求。

    而且没有邮筒的话,而且每次邮递员都需要找我们取信,效率低下。

    假如邮递员处理较慢我们只能等着极其不方便,如果有了邮筒邮递员比较忙我们可以先放在邮筒里等邮递员慢慢处理,这样不妨碍我们寄信。

    接下来我们梳理下生产者消费者模式的基本思路。

    要实现消费者生产者模式我们还需要知道几个函数

    1.wait(),让当前线程等待且会释放对象锁,这时wait()之后的语句不会执行。只有被其他线程用notify或notifyall唤醒,才能重新抢夺锁,获得锁后从wait()方法之后的路径继续执行

    如果没有被其他线程唤醒,就不会醒来。

    2.nitify()通知在此对象监视器上等待的单个线程醒来,只是通知而且通知是随机的。当前对象监视器上等待的线程获得通知后会金进入等待队列抢夺对象锁。

    3.notifyall唤醒在此对象监视器上等待的所有线程。

    注意上面的函数都需要获得对象锁,所以需要synchronized配合使用。

    接下来就是具体的代码了:

     缓冲区:

    public class Buffer {
        private int index = 0;
        private int MAX = 5;                //定义缓冲区的最大存储量
        private Phone pa[] = new Phone[MAX];//定义存储产品的数组
        
        synchronized public void consumer(){  //消费者方法,要加synchronized获得锁,消费线程会进缓冲区拿东西。
            if(index <= 0){  //如果缓冲区没有资源,当前线程等待并释放锁。
                try {           
                    wait();  //如果该线程被其他线程唤醒并获得锁,从wait()之后继续执行
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } 
            
            /**
             * 消费时一定要注意先--,因为必定先生产后才能消费,
             * 而生产后由于++的原因会多加一个1,所以这里需要多减一个1.
             * 仓库有货或生产者线程唤醒后消费产品。
             * 生产者是先生产然后唤醒消费者,所以唤醒消费者后的缓冲区必有货物.
             */
            
            //从缓冲区拿手机,即消费手机。
            System.out.println("消费手机:" + pa[--index].getPhoneNum()); 
            notify(); //消费后唤醒生产者线程。  
            
    /** * 这里的唤醒只是通知下可以抢夺锁了,但也不一定抢到。 * 如果生产者没有抢到锁消费者会继续消费。 * 如果没有货了,消费者自己会等待,然后生产者自然会去生产。 * 锁只有执行完synchronized范围才会释放锁。wait()是可以直接立刻马上释放锁, * 而且之后的语句也不执行,之后的语句等到下一次被其他线程唤醒并获得锁后才执行。 */ } synchronized public void producer(Phone p){//生产者,接受来自生产线程生产的产品。 if(index >= MAX){ //如果缓冲区已满,则释放锁并等待。 try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
    //将生产的产品放在数组中,放入后要加1 pa[index++] = p; //仓库没有满或被消费者线程唤醒后才能生产。 //被消费者叫醒说明 消费者消费了产品,则缓冲区必定没有满. System.out.println("生产手机:" + p.getPhoneNum()); notify(); //生产后,唤醒消费者线程。 } }

     产品:

    public class Phone {
    	private int num;
    	
    	public Phone(int num){  //构造器设置手机编号
    		this.num = num;
    	}
    	
    	public int getPhoneNum(){  //产品中有一个取得当前手机编号的方法。
    		return num;
    	}
    }
    

     

    生产者

    public class Producer implements Runnable{  //生产者线程需要实现Runnable接口
        private int i = 0;
        private Buffer b;                  //创建缓冲区对象。
        
        public void setBuffer(Buffer b){   //添加构造方法获得缓冲区对象。
            this.b = b;
        }
        
        
        public void run(){                 //生产者线程生产20个产品
            for(i = 0; i < 20; i++){
                b.producer(new Phone(i));  //生产者线程将产品放入缓冲区
            }
        }    
    }

    消费者

    public class Consumer implements Runnable{
        private Buffer b;
        
        public void setBuffer(Buffer b){  //通过方法设置缓冲区对象
            this.b = b;
        }
        
        public void run(){               //消费者线程
            for(int i = 0; i < 20; i++){  //消费20个产品
                b.consumer();             //进入缓冲区拿产品
            }
        }
    }

    客户端

    public class ProCon {
        public static void main(String[] args) {
            Buffer b = new Buffer();  //建立缓冲区
            
            Consumer c = new Consumer(); //建立消费者线程
            c.setBuffer(b);              //在消费者线程中放入缓冲区对象
            Producer p = new Producer();  //建立生产者线程
            p.setBuffer(b);               //在生产者线程中放入缓冲区对象
            
            new Thread(c).start();
            new Thread(p).start();
        }
    }
    运行结果:
    生产手机:0
    生产手机:1
    生产手机:2
    生产手机:3
    生产手机:4
    消费手机:4
    消费手机:3
    消费手机:2
    消费手机:1
    消费手机:0
    生产手机:5
    生产手机:6
    生产手机:7
    生产手机:8
    生产手机:9
    消费手机:9
    消费手机:8
    消费手机:7
    消费手机:6
    消费手机:5
    生产手机:10
    生产手机:11
    生产手机:12
    生产手机:13
    生产手机:14
    消费手机:14
    消费手机:13
    消费手机:12
    消费手机:11
    消费手机:10
    生产手机:15
    生产手机:16
    生产手机:17
    生产手机:18
    生产手机:19
    消费手机:19
    消费手机:18
    消费手机:17
    消费手机:16
    消费手机:15

    由于这里缓冲区的容量为5,所以每生产5个缓冲区就满了,然后会通知生产者会等待,消费者回来消费。

    当消费者消费了5个后没有货物了,消费者会等待,这时生产者又会来生产。

    这只是恰好一个线程运行的时间可以生产完5个或者消费5个,在实际中的顺序是不确定的。

    但有一点可以确定,必定是生产好了后的产品才能消费。

    而且必定是缓冲区满了生产者会停下,缓冲区没有货物了消费者会停下。

  • 相关阅读:
    在oracle配置mysql数据库的dblink
    项目中非常有用并且常见的ES6语法
    原生js的容易忽略的相似点(二)
    原生js的容易忽略的相似点(一)
    json常用方法和本地存储方法
    vue-cli下面的config/index.js注解 webpack.base.conf.js注解
    vue跨域解决及打包
    js里面Object的一些方法
    vw+vh+rem响应式布局
    toast插件的简单封装(样式适用pc后台管理系统的场景)
  • 原文地址:https://www.cnblogs.com/huang-changfan/p/9470210.html
Copyright © 2011-2022 走看看