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(); } } pa[index++] = p; //将生产的产品放在数组中,放入后要加1 ,仓库没有满或被消费者线程叫醒(被消费者叫醒说明消费者消费了产品,则缓冲区必定没有满)后生产产品。 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,所以每生产五个缓冲区就满了,然后会通知生产者会等待,消费者回来消费。
当消费者消费了五个后没有货物了,消费者会等待,这时生产者又会来生产。
这只是恰好一个线程运行的时间可以生产完五个或者消费五个,在实际中的顺序是不确定的。
但有一点可以确定,必定死生产好了后的产品才能消费。
下一篇: java 第五章 方法定义及调用
推荐阅读
-
Java多线程-同步:synchronized 和线程通信:生产者消费者模式
-
Java 学习笔记 使用并发包ReentrantLock简化生产者消费者模式代码
-
Java 学习笔记 使用synchronized实现生产者消费者模式
-
2.5多线程(Java学习笔记)生产者消费者模式
-
java中的多线程的实现生产者消费者模式
-
Java多线程 - (三) 线程间的通信(协作) - 生产者/消费者模式
-
java多线程-Producer-consumer(生产者消费者模式)
-
java多线程消费者生产者模式(BlockingQueue 通过阻塞队列实现)
-
2.5多线程(Java学习笔记)生产者消费者模式
-
java多线程之并发协作生产者消费者设计模式