java-多线程(二)-生产者与消费者

什么是生产者消费者模型?

举个例子:以DOTA来说,辅助进行拉野,大哥收野,辅助就是生产者,大哥就是消费者,辅助拉一波,大哥清一波.这两个配合起来,就是生产者与消费者

但是这样效率很低,毕竟一波野怪还不够让大哥挪动的,所以我们对其进行优化,加入队列的思想.

生产者消费者模型队列:

以一个饭店为例,厨师做一个菜,服务员传一个菜,但是在这个时候出现了问题,如果生产速度过快,消费能力过弱,或者生产能力过弱,消费能力过强,有一方就需要等待,会造成资源的浪费.

下边就要引入生产者与消费者模型的队列,上边的例子,厨师就是生产者,服务员就是消费者,但是两者不直接进行通讯,而是通过一个缓冲区,生产者对数据进行生产之后将其放入缓冲区,而消费者直接从缓冲区取数据,两者并不直接进行通讯.

而这个缓冲区,就通过阻塞队列来实现, 队列的特性就是先进先出,所以不会出现后生产的数据被先拿走,或者先生产的数据没有被使用的情况.

下面通过一个例子来了解一下简单的生产者消费者模型.

这里我们使用的队列为BlockingQueue,线程安全,也就是同一时刻,只允许一个线程对其进行访问,不会同时出现多个线程访问的情况,它将会阻塞线程.

先上代码:

实体类:
package producerConsumer;

//测试用实体类
public class Food {

private String name;//食物名称

private double price;//食物价格

public Food(String name, double price) {
this.name = name;
this.price = price;
}

@Override
public String toString() {
return "Food{" +
"name='" + name + ''' +
", price=" + price +
'}';
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}
}


消费者
public class ConsumerRunner implements Runnable {

@Override
public void run() {
try {

while (!Thread.currentThread().isInterrupted()) {

System.out.println(foodBlockingQueue.take().toString());//从队列中拿取元素
TimeUnit.MILLISECONDS.sleep(200);

}
} catch (InterruptedException e) {
System.out.println("interrupted");
}

System.out.println("Ending of ConsumerRunner");

}
}


生产者:
public class ProducerRunner implements Runnable {

//生产数据并且向队列添加
@Override
public void run() {

try {
while (!Thread.currentThread().isInterrupted()) {

Food food = new Food("鸡肉", 50.25);//新建food实体
System.out.println("生产成功");
foodBlockingQueue.put(food);//填入队列

TimeUnit.MILLISECONDS.sleep(200);

}
}catch(InterruptedException e){

System.out.println("interrupted");

}
System.out.println("Ending of ProducerRunner");
}
}

测试用类:

//测试用类
public class pcTest {

public static BlockingQueue foodBlockingQueue = new LinkedBlockingQueue() ;//静态队列方便全局调用

public static void main(String[] args) throws InterruptedException {

ExecutorService exec = Executors.newCachedThreadPool();//创建线程池

exec.execute(new ProducerRunner());//启动生产者线程

exec.execute(new ConsumerRunner());//启动消费者线程

TimeUnit.MILLISECONDS.sleep(2000);//延缓main()线程时间,体现效果

exec.shutdownNow();//终止exec旗下所有线程.

}
}

结果:

生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
interrupted
interrupted
Ending of ProducerRunner
Ending of ConsumerRunner

下面说一个稍微复杂的情况,当你既是消费者,又是生产者的时候,并且需要通过你消费的数据从而加工生成新的数据,举个例子比如菜农是生产者,饭店是消费者, 而饭店又是菜品的生产者,下级的消费者是服务员(顾客点菜传菜用),这个时候单向的队列就不能满足需求了.

下面看一个经典的吐司blockingQueue案例

实体类吐司:

public class Toast {

public enum Status{DRY,BUTTERED,JAMMED}//面包状态,白面包,抹了黄油的,抹了果酱的

private Status status= Status.DRY;//默认是白面包

private final int id;

public Toast(int id){
this.id=id;//吐司ID
}

//抹黄油!
public void butter(){

this.status=Status.BUTTERED;
}

//抹果酱!
public void jammed(){
this.status=Status.JAMMED;
}

public Status getStatus() {
return status;
}

public int getId() {
return id;
}

@Override
public String toString() {
return "Toast{" +
"status=" + status +
", id=" + id +
'}';
}
}

做吐司的:

public class Toaster implements Runnable {

private ToastQueue toastQueue;//一个吐司队列

private int count=0;//计数器

private Random random =new Random(47);//随机

public Toaster(ToastQueue tq){
toastQueue = tq;//通过有参构造传入队列
}

@Override
public void run() {

try {
while (!Thread.currentThread().isInterrupted()) {
TimeUnit.MILLISECONDS.sleep(100 + random.nextInt(500));//做吐司

Toast t = new Toast(count++);//从0开始 每生产一个编号+1 把编号传入当做吐司ID

System.out.println(t.toString());

toastQueue.put(t);

}
}catch(InterruptedException e){

System.out.println("线程中断");

}

System.out.println("吐司制作结束,没面粉了!");

}
}

给白面包抹黄油的:

//黄油怪,涂抹黄油
public class Butterer implements Runnable {

private ToastQueue dryQueue,butteredQueue;//建立白面包队列和黄油面包队列

//这里解释一下,因为这里抹黄油的既是白吐司的消费者,也黄油面包的生产者,况且,黄油面包还可以被别人消费,所以这里要声明两个队列

public Butterer(ToastQueue dryQueue,ToastQueue butteredQueue) {

this.dryQueue =dryQueue;
this.butteredQueue = butteredQueue;
}

@Override
public void run() {

try{
while(!Thread.currentThread().isInterrupted()){

Toast toast = dryQueue.take();//拿白面包
toast.butter();//抹黄油
TimeUnit.MILLISECONDS.sleep(200);//模拟抹黄油
System.out.println(toast.toString());//打印状态
butteredQueue.put(toast);//将黄油吐司放入队列.
}
}catch(InterruptedException e){

System.out.println("黄油中断");

}

System.out.println("抹黄油结束");

}
}

给黄油吐司抹果酱的:

public class janmmer implements Runnable {

private ToastQueue butteredQueue,finishQueue;//黄油面包,抹果酱结束.

public janmmer(ToastQueue butteredQueue, ToastQueue finishQueue) {
this.butteredQueue = butteredQueue;
this.finishQueue = finishQueue;
}

@Override
public void run() {

try{

while(!Thread.currentThread().isInterrupted()){

Toast t = butteredQueue.take();//拿黄油吐司
t.jammed();//抹果酱
TimeUnit.MILLISECONDS.sleep(200);
System.out.println(t.toString());//打印状态
finishQueue.put(t);//扔进结束队列

}

}catch(InterruptedException e){

System.out.print("抹果酱中断");
}

System.out.println("抹果酱程序结束");

}
}

吃吐司的人:

public class Eater implements Runnable {

private ToastQueue finishQueue;

private int counter=0;

public Eater(ToastQueue finishQueue) {
this.finishQueue = finishQueue;
}

@Override
public void run() {

try{
while(!Thread.currentThread().isInterrupted()){
Toast t =finishQueue.take();//拿面包
if(t.getId()!=counter++||t.getStatus()!=Toast.Status.JAMMED){
//如果面包编号不对(顺序上错了),或者面包的没有涂果酱,会报错

System.out.println("错误: 这不是我要的面包!,他不是果酱面包!");
}else{

System.out.println("真,真香"+t.toString());
}
}
} catch(InterruptedException e) {
System.out.println("中断,不吃了");
}
System.out.println("用餐结束");
}
}

吐司队列,方面书写,语义明确:

public class ToastQueue extends LinkedBlockingQueue {}

测试类:

public class ToastTest {

public static void main(String[] args) throws InterruptedException {

ToastQueue dryToast =new ToastQueue();
ToastQueue butteredToast = new ToastQueue();
ToastQueue finToast=new ToastQueue();
//创建三个吐司队列
ExecutorService exec = Executors.newCachedThreadPool();//创建线程池
exec.execute(new Toaster(dryToast));//做吐司
exec.execute(new Butterer(dryToast,butteredToast));//抹黄油
exec.execute(new janmmer(butteredToast,finToast));//抹果酱
exec.execute(new Eater(finToast));//吃

TimeUnit.MILLISECONDS.sleep(2000);

exec.shutdownNow();

}
}

测试结果:
Toast{status=DRY, id=0}
Toast{status=DRY, id=1}
Toast{status=BUTTERED, id=0}
Toast{status=JAMMED, id=0}
真,真香Toast{status=JAMMED, id=0}
Toast{status=BUTTERED, id=1}
Toast{status=DRY, id=2}
Toast{status=JAMMED, id=1}
真,真香Toast{status=JAMMED, id=1}
Toast{status=BUTTERED, id=2}
Toast{status=JAMMED, id=2}
真,真香Toast{status=JAMMED, id=2}
Toast{status=DRY, id=3}
Toast{status=BUTTERED, id=3}
Toast{status=JAMMED, id=3}
真,真香Toast{status=JAMMED, id=3}
Toast{status=DRY, id=4}
中断,不吃了
用餐结束
黄油中断
抹果酱中断抹果酱程序结束
线程中断
吐司制作结束,没面粉了!
抹黄油结束

由于BlockingQueue是阻塞队列,并且由于队列的特性,我们可以看出食客是按照顺序吃的,这样不需要线程同步,中间每个对象传递都是唯一顺序,从队列中取也只能按照队列的顺序来.

这个例子准确来说不是特别严谨,体会其中生产者与消费者的关系,其中也没有对中断后的数据进行后续清理操作,主要表达消费者同时是生产者时,通过队列来交互信息的思想.

posted @ 2018-05-25 14:44  CurryRice  阅读(364)  评论(0)    收藏  举报