在生活中,供求关系是很常见的。一个卖烧鸭的,一个买烧鸭的,卖烧鸭的要有烧鸭,买烧鸭的才能买到烧鸭。所以,买烧鸭的要等卖烧鸭的准备好烧鸭,她才有得买。这就是典型的生产者/消费者模式呀。用Java线程描述这些事情,也是可以妥妥的。
王大厨开了一间烧鸭店,每天生产100只烧鸭。张大妈今天家里摆酒请客,刚好要买100只烧鸭。王大厨做出的烧鸭都会挂在一个挂钩上,这些挂钩就像是一条队列,一只只烧鸭挂在上面,先做出来的烧鸭摆在前面卖给客人。当挂钩上没有烧鸭的时候,客人就必须等待王大厨重新做出烧鸭。
那么在Java的世界里,这件事情将会如下面代码呈现。
(1)首先是无辜的烧鸭,用下面的类Duck
表示,每个烧鸭有一个num
字段代表编号
/**
* 烧鸭
*/
public class Duck {
private int num;
//..其它属性
public Duck(int Num) {
this.num = Num;
}
@Override
public String toString() {
return "[" + num+"号烧鸭]";
}
}
(2)然后是鸭子队列(就是挂钩挂着的一个个鸭子~),用下面的类DuckQueue
表示,其中用LinkedList
类型(FIFO)的字段queue
来“挂”烧鸭~其中有两个很重要的方法
getDuck
方法用来获取队列queue
中的Duck
对象,注意了,queue.removeFirst();
会把队列的第一个Duck
对象移出队列。其中有一步很重要,就是while
中的判断,当队列中有对象才能获取对象,就像是有烧鸭,别人才可以买。没有怎么办?等呗,wait
方法就是让当前线程等着,等别人叫他。就像是王大厨看没烧鸭了,先叫张大妈等着,等有了再告诉张大妈,让她拿走。putDuck
方法用来将一个Duck
对象放进队列queue
中,当然了是放在队列的最后面,当放完后,就用notifyAll
通知等待中的线程。就像是王大厨每做出一只烤鸭都要告诉一声张大妈,有烧鸭了~不管之前挂着有没有,反正就是要通知一声。
注意,这两个方法必须是同步方法,因为他们都在操作queue,而且wait
和notifyAll
方法必须在同步方法或同步代码块中执行。
import java.util.LinkedList;
/**
* 烧鸭队列
*/
public class DuckQueue {
// 存放Duck对象
private LinkedList<Duck> queue = new LinkedList<Duck>();
/**
*获取queue中的Duck对象
*/
public synchronized Duck getDuck(){
while(queue.size() <= 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return queue.removeFirst();
}
/**
*往 queue中添加Duck对象
*/
public synchronized void putDuck(Duck duck){
queue.addLast(duck);
notifyAll();
}
}
(3)然后就是生产者类,就是做烤鸭的,用下面的类Producer
表示。其中random
只是用来生成随机数,然后用这个设置线程睡眠时间,模拟现实中生产烧鸭所需的时间各不相同,下面的消费者类也是如此。Producer
是一个线程类,在run
方法中依次生产100只烧鸭并挂在队列上。
import java.util.Random;
/**
* 生产者
*/
public class Producer extends Thread {
private DuckQueue queue;
private Random random;
public Producer(String name, DuckQueue queue, Random random) {
super(name);// 设置线程名称
this.queue = queue;
this.random = random;
}
public void run() {
for (int i = 1; i <= 100; i++) {
Duck duck = new Duck(i);// 产生烧鸭
queue.putDuck(duck);// 挂上烧鸭
System.out.println(Thread.currentThread().getName() + "生产了" + duck);
int sleepTime = random.nextInt(100);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(4)消费者类,就是拿走烧鸭的,用下面的类Consumer
表示。Consumer
是一个线程类,在run
方法中拿走100个烧鸭,不过是一次拿一个。
/**
* 消费者
*/
public class Consumer extends Thread{
private DuckQueue queue;
private Random random;
public Consumer(String name,DuckQueue queue,Random random) {
super(name);//设置线程名称
this.queue = queue;
this.random = random;
}
public void run() {
for (int i = 1; i <= 100; i++) {
Duck duck = queue.getDuck();// 从队列中拿走烧鸭
System.out.println(Thread.currentThread().getName() + "拿走了"+duck);
int sleepTime = random.nextInt(100);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(5)最后是测试程序,用下面的类Main
表示。其中创建Producer
对象时所用的数字321654987
没有什么特别的意义,只不过是用于随机数的生成,123456798
亦是如此。
public class Main {
public static void main(String[] args) {
DuckQueue queue = new DuckQueue();
new Producer("王大厨", queue, new Random(321654987)).start();
new Consumer("张大妈", queue, new Random(123456798)).start();
}
}
(6)最后的输出结果,只截取了前面一段和后面一段。
张大妈拿走了[1号烧鸭]
王大厨生产了[1号烧鸭]
王大厨生产了[2号烧鸭]
王大厨生产了[3号烧鸭]
张大妈拿走了[2号烧鸭]
王大厨生产了[4号烧鸭]
张大妈拿走了[3号烧鸭]
王大厨生产了[5号烧鸭]
张大妈拿走了[4号烧鸭]
张大妈拿走了[5号烧鸭]
王大厨生产了[6号烧鸭]
张大妈拿走了[6号烧鸭]
王大厨生产了[7号烧鸭]
张大妈拿走了[7号烧鸭]
王大厨生产了[8号烧鸭]
王大厨生产了[9号烧鸭]
....
王大厨生产了[92号烧鸭]
张大妈拿走了[91号烧鸭]
王大厨生产了[93号烧鸭]
王大厨生产了[94号烧鸭]
王大厨生产了[95号烧鸭]
张大妈拿走了[92号烧鸭]
张大妈拿走了[93号烧鸭]
王大厨生产了[96号烧鸭]
张大妈拿走了[94号烧鸭]
王大厨生产了[97号烧鸭]
张大妈拿走了[95号烧鸭]
张大妈拿走了[96号烧鸭]
王大厨生产了[98号烧鸭]
张大妈拿走了[97号烧鸭]
王大厨生产了[99号烧鸭]
张大妈拿走了[98号烧鸭]
王大厨生产了[100号烧鸭]
张大妈拿走了[99号烧鸭]
张大妈拿走了[100号烧鸭]
从上面的输出结果大家可以看出,这就是我们期待的结果王大厨生产出烧鸭,张大妈拿走,两者不会搞混,不会发生王大厨还没生产出2号烧鸭而张大妈就拿走2号烧鸭或者更后编号的烧鸭这种鬼事。
总结一下,在整个程序中,最妙的地方在于交流,两条线程就像人一样在通话。虽然通的话只有几句话。
张大妈:“诶,现在烧鸭队列里有烧鸭嘛”—> [while(queue.size() <= 0)
]
张大妈:“没呀,好吧,我等着”(while中的判断为true的时候)—> [wait();
]
张大妈:“有了,好吧,我拿走咯~”(while中的判断为false的时候)—>[queue.removeFirst();
]
王大厨:“有烧鸭了,快来拿”—> [queue.addLast(duck);notifyAll
]
其实上面的程序还可以变形一下,就是说王大厨做出一只烧鸭张大妈拿一只,如果张大妈不拿,则王大厨就不做先,知道张大妈拿走再做。如果是这样的话只需要将DuckQueue
类写成下面这样子,也是“通话”的形式,只不过条件变了。
/**
* 烧鸭队列
*/
public class DuckQueue {
// 存放Duck对象
private LinkedList<Duck> queue = new LinkedList<Duck>();
/**
*获取queue中的Duck对象
*/
public synchronized Duck getDuck(){
while(queue.size() != 1){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Duck duck = queue.removeFirst();
notifyAll();
return duck;
}
/**
*往 queue中添加Duck对象
*/
public synchronized void putDuck(Duck duck){
while(queue.size() != 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(duck);
notifyAll();
}
}
好了,这就是生产者/消费者模式,当然其中还有很多不同的实现方法,有些方法更优。不过这种模式的思想就是这样子,你生产来我消费,你没东西来我等待,你有东西来记得叫我。