生产者消费者问题包含三个成分:
1.生产者,用于提供数据 生产者类
2.消费者,用于拿走数据 消费者类
3.共享资源,就像是一个仓库,消费者把提供的数据放进去,生产者拿走数据。(用于解耦生产者和消费者的关系) 奶箱类
以下是一个实例
我们想喝牛奶但是又不想去商店买(生产者是商店,消费者是我,路径是 生产者->消费者),因此我们就选择了订奶
送奶工早上将牛奶从商店送来我手里,但是我早上又起不来那么早,难道就无法拿到牛奶了吗?
不,送奶工会将牛奶送到我家门口的奶箱,等我起床后只需要去奶箱里面拿就好了,这个过程我与送奶工并没有直接的见面(生产者与消费者没有变,但是我们两个中间多了一个中介:送奶工与奶箱,这时的路径就是:生产者->传递数据(送奶工)->共享资源(奶箱)->拿走数据(我)->消费者)
而在我们的代码编写过程中,生产者->传递数据->共享资源->拿走数据->消费者,我们这样建立:
生产者类 实现Runnable接口,重写run()方法,且传入奶箱类对象【一条传入数据的路径】
消费者类 实现Runnable接口,重写run()方法,且传入奶箱类对象【一条拿走数据的路径】
奶箱类 包含表示奶箱大小,奶箱状态等的成员变量,及传入数据的方法和拿走数据的方法
测试类 建立对象并使用多线程实现
代码如下:(这里共享资源默认大小为一,只有放入与不放入)
//奶箱类
public class Box {
private int milk;
private boolean state=false;定义一个成员变量表示奶箱的状态,false表示没有牛奶
//下面使用的存放方式是默认奶箱容量为一
public synchronized void put(int milk) {
//如果有牛奶,等待消费
if(state) {
try {
wait();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
//如果没有牛奶,生产牛奶
this.milk=milk;
System.out.println("送奶工正在送第"+milk+"瓶奶");
//生产完毕之后修改奶箱
state=true;
notifyAll();
}
public synchronized void get() {
/*“线程操作的wait()、notify()、notifyAll()方法只能在同步控制方法或同步控制块内调用。
* 如果在非同步控制方法或控制块里调用,程序能通过编译
* 但运行的时候,将得到 IllegalMonitorStateException 异常,并伴随着一些含糊信息,比如 ‘当前线程不是拥有者’。
* 其实异常的含义是 调用wait()、notify()、notifyAll()的任务在调用这些方法前必须 ‘拥有’(获取)对象的锁。*/
//如果有牛奶,等待拿走
if(!state) {
//如果没有牛奶,等待生产
try {
wait();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
//如果有牛奶,消费牛奶
System.out.println("用户拿走第"+milk+"瓶奶");
state=false;//牛奶被拿走
notifyAll();
}
}
//生产者类
public class Producer implements Runnable {
private Box box;
public Producer(Box box) {
this.box=box;
}
public void run() {
for(int i=1;i<=5;i++) {//通过这里定义放入奶的数量
box.put(i);
}
}
}
//消费者类
public class Customer implements Runnable {
private Box box;
public Customer(Box box) {
this.box=box;
}
public void run() {
while(true) {//这里用无限循环的方式是因为奶箱类里面定义的get()方法含有判断
box.get();
}
}
//测试类
public class BoxDemo {
public static void main(String[] args) {
Box box=new Box();//共享数据对象
Producer producer=new Producer(box);//生产者对象,使用box做参数
Customer customer=new Customer(box);//消费者对象,使用box做参数
//创建两个线程
Thread t1=new Thread(producer);
Thread t2=new Thread(customer);
t1.start();
t2.start();
}
}
再对比一下下面的代码,多定义了一个size表示奶箱的大小
public class Box {
private int milk=9;
static final int size=3;//奶箱一次最多放入三瓶奶
static int state=0;定义一个成员变量表示奶箱中的奶数
public synchronized void put() {
//如果牛奶数等于奶箱容积,等待消费
if(state==size) {
try {
wait();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
for(;state<size&&state>=0;state++) {
//如果奶箱中的牛奶数小于奶箱容积,生产者继续生产并放入,每放入一瓶奶箱中的奶数量加一,直到奶数达到容积,停止放入
System.out.println("送奶工正在送第"+milk+"瓶奶");
milk--;
}
notifyAll();
}
public synchronized void get() {
/*“线程操作的wait()、notify()、notifyAll()方法只能在同步控制方法或同步控制块内调用。
* 如果在非同步控制方法或控制块里调用,程序能通过编译
* 但运行的时候,将得到 IllegalMonitorStateException 异常,并伴随着一些含糊信息,比如 ‘当前线程不是拥有者’。
* 其实异常的含义是 调用wait()、notify()、notifyAll()的任务在调用这些方法前必须 ‘拥有’(获取)对象的锁。*/
for(;state>0&&state<=size;state--) {//如果有牛奶,消费牛奶
System.out.println("用户拿走奶箱中的第"+state+"瓶奶");
}
notifyAll();
if(state==0) {
//如果没有牛奶,等待生产
try {
wait();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
public class Customer implements Runnable {
private Box box;
public Customer(Box box) {
this.box=box;
}
public void run() {
for(int i=1;i<3;i++) {//这里的i实际上是milk数除以size数
//这个程序并不完善,没有判断当milk不能整除size时,送奶工如何送奶及用户如何拿奶
//这时还需要在put方法里面加一个判断
//if(milk/size!=0)
//size=milk%size;
box.get();
}
}
}
public class Producer implements Runnable {
private Box box;
public Producer(Box box) {
this.box=box;
}
public void run() {
while(true) {
box.put();
}
}
}
public class BoxDemo {
public static void main(String[] args) {
Box box=new Box();//共享数据对象
Producer producer=new Producer(box);//生产者对象,使用box做参数
Customer customer=new Customer(box);//消费者对象,使用box做参数
//创建两个线程
Thread t1=new Thread(producer);
Thread t2=new Thread(customer);
t1.start();
t2.start();
}
}
结果如下:
送奶工一次送三瓶奶,即共享资源大小为三
送奶工正在送第9瓶奶
送奶工正在送第8瓶奶
送奶工正在送第7瓶奶
用户拿走奶箱中的第3瓶奶
用户拿走奶箱中的第2瓶奶
用户拿走奶箱中的第1瓶奶
送奶工正在送第6瓶奶
送奶工正在送第5瓶奶
送奶工正在送第4瓶奶
用户拿走奶箱中的第3瓶奶
用户拿走奶箱中的第2瓶奶
用户拿走奶箱中的第1瓶奶
送奶工正在送第3瓶奶
送奶工正在送第2瓶奶
送奶工正在送第1瓶奶