生产者消费模式 : 顾名思义有生产者、消费者、资源 三个对象。生产者生产资源,消费者消费资源。
类似于工厂流水线,多条生产线(生产者),多条包装线(消费线)。
前面有介绍线程的 等待唤醒机制 我们可以回顾下,一条输入线程(生产者),一条输出线程(消费者),不同线程对同一资源进行操作,只不过操作的工作不一致。
回顾下等待唤醒机制的代码(2条线程)
//资源文件
class Resources
{
private String name;
private int count = 1;
private boolean flag = false; //标志位
//生产函数
public void synchronized setName(String name)
{
if(flag)
{ try{this.wait();} try(Exception e){} }
this.name = name + count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
falg = true;
this.notify();
}
//消费函数
public void synchronized out()
{
if(!flag)
{ try{this.wait();} try(Exception e){} }
System.out.println(Thread.currentThread().getName()+" <----消费者---> "+this.name);
flag = false;
this.notify();
}
}
// 生产者文件
class Producter implements Runnable
{
private Resources res;
public Producter(Resources res)
{
this.res = res;
}
//实现run方法
public void run()
{
res.setName("苹果");
}
}
//消费者文件
class Consumer implements Runnable
{
private Resources res;
public Consumer(Resources res)
{
this.res = res;
}
//实现run方法
public void run()
{
res.out();
}
}
//main方法
public static void main(String [] args)
{
//资源
Resources res = new Resources();
//线程对象
Thread t1 = new Thread(new Producter(res));
Thread t2 = new Thread(new Consumer(res));
//开启线程
t1.start();
t2.start();
}
以上代码运行是们问题的,正常的生产线程生产一个资源,消费线程消费一个资源,有序间隔输出。但是当我们增加线程数继续运行的时候,就发现打印的结果不对了。
在main方法修改如下
public static void main(String[] args)
{
//创建资源
Resource res = new Resource();
//生产者线程
Thread t1 = new Thread(new Producter(res));
Thread t2 = new Thread(new Consumer(res));
//消费者线程
Thread t3 = new Thread(new Producter(con));
Thread t4 = new Thread(new Producter(con));
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
.
截取部分输出结果不匹配截图如下
.
.
生产2个,只消费了其中1个。或者消费两个,只生产一个。……为什么会出现如上问题呢?我们可以分析下问题
.
假设代码执行流程如下(解释下生产2次,消费一次)
生产线程 : t1、t2
消费线程 :t3、t4
(1) 假设生产者率先获取cpu的执行权,生产者有2个。假设t1先获取执行权,t1判断标志位flag = false,先生产一个(打印一次),后将flag = true,并notify()后。此时t1还持有执行权,继续回来判断flag = true, 符合条件,t1就直接wait了,放弃资格。此时wait的线程就直接进线程池(数据结构为链表结构,先进先出)
(2) t2、t3、t4,假设生产者t2继续获取cpu执行权,判断标志位flag = true,符合条件,t2也wait了,放弃资格。(此时线程池里有t1、t2)
(3) 只剩下t3、t4。 t3先获取执行资格,判断标志位!falg = false,不符合条件,继续往下执行,消费一个。并把标志位设置成falg = false,并notofy()下。后,t3继续享有执行权,回来继续判断标志位 !flag = true 。符合条件,t3就直接wait了。失去执行资格,进入线程池。(此时线程池有t1、t2、t3)。
(4) 上一步骤 t3 执行完后,notify(),此时优先唤醒了生产线程t1。t1 线程在刚wait地方继续往下执行(注意:t1不继续判断标志位了),生产一个。并继续notify(),此时唤醒的是t2线程,t2也不继续判断,继续往下执行,又生产一个。t2唤醒的是消费者t3。t3 就消费一个。此时,就会产生生产两个,只消费一个的现象。
其他情况自己类比解释。
那问题来了? 为什么会造成如上问题呢 ?
根据上述执行步骤分析,我们可以知道,如果t3执行后,唤醒了t1时,t1执行完成后,t1又唤醒t2,如果在唤醒前我们让线程 重新去判断标志位,那就不存在连续生产两个的情况。 也就是说,把 if() ,修改成 while() 。
//资源类文件
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
// 生产函数
public synchronized void setName(String name)
{
while(flag) { //每次执行都判断标记
try {wait();}catch(Exception e) {}
}
this.name = name+"..."+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notify();
}
// 消费函数
public synchronized void out()
{
while(!flag) //每次执行都判断标记
{
try {wait();}catch(Exception e) {}
}
System.out.println(Thread.currentThread().getName()+" <----消费者---> "+this.name);
flag = false;
this.notify();;
}
}
.
.
运行下看下打印结果,你会发现,全部线程都wait了。
怎么解决呢?Java在Object类里面还有个方法notifyAll()方法。唤醒全部。全唤醒后,他们都会循环去判断标记,只要循环判断标记,就知道什么时候该执行,什么时候该wait。
//资源类文件
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
// 生产函数
public synchronized void setName(String name)
{
while(flag) {
try {wait();}catch(Exception e) {}
}
this.name = name+"..."+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notifyAll();
}
// 消费函数
public synchronized void out()
{
while(!flag)
{
try {wait();}catch(Exception e) {}
}
System.out.println(Thread.currentThread().getName()+" <----消费者---> "+this.name);
flag = false;
this.notifyAll();;
}
}
.
.
运行结果如下(只截取部分截图)
总结 :当出现多个生产者和消费者必须
(1) while 循环判断标记
(2) notifyAll () 既唤醒本方又唤醒对方