生产者与消费者是运用多线程协作,其中生产者不断生产产品,将这些产品放入指定的仓库(或大的容器),消费者是从仓库中取得产品。当仓库中产品放满时,生产者则需要停止生产;当仓库中没有产品时,消费者则需要停止消费,除非仓库中有产品。本实例介绍生产者不断采集鲜花放入花篮中,消费者不断从花篮中取出鲜花。
运用多线程模拟生产者与消费者的技术要点如下:
有synchronized的地方不一定有wait()与notify()方法,有wait()与notify()方法的地方必有synchronized,这是因为wait()方法和notify()方法不属于线程类,而是每一个对象都有的方法,而且,这两个方法都和对象锁有关,有锁的地方,必有synchronized。
当仓库中产品数量为0时,调用wait()方法,使得当前消费者线程进入等待状态;当有新的产品时,调用notify()方法,唤醒等待的消费者线程;当仓库中的产品放满时,调用wait()方法,使得当前的生产者线程进入等待状态,当消费者取得产品时,调用notify()方法,唤醒等待的生产者线程。
package core;
import java.util.Random;
class Flower { // 缓存数据,用于存与取数据的类(花篮)
private final String[] data;
private int tail;// 字符串数组的尾部,用于放置put鲜花
private int head;// 字符串数组的头部,用于取出take鲜花
private int count;// 缓存内的鲜花数
public Flower(int count) { // 创建并初始化鲜花数组
this.data = new String[count];
this.head = 0;
this.tail = 0;
this.count = 0;
}
public synchronized void put(String flowerId) {// 放置鲜花
try {
while (count >= data.length) {
wait();
}
data[tail] = flowerId; // 把鲜花flowerId放在数组尾部
tail = (tail + 1) % data.length; // 数组尾部下标加1,指向下一个空的位置
count++; // 鲜花放置加1
notifyAll();// 通知消费者取鲜花产品
System.out.println("当前线程" + Thread.currentThread().getName() + "放置鲜花," + flowerId);
} catch (InterruptedException e) {
System.out.println("放置鲜花异常 " + e.getMessage());
}
}
public synchronized String take(){
String flower=null;
try {
while(count<=0){
wait();
}
flower=data[head];//取出数组头部的鲜花产品
head=(head+1)%data.length; //数组头部加1
count--; //数组实际存储的鲜花产品减一
notifyAll();//通知所有的生产者
} catch (Exception e) {
System.out.println("消费者取出鲜花异常 " + e.getMessage());
}
System.out.println("当前线程"+ Thread.currentThread().getName()+"取出鲜花"+flower);
return flower;
}
}
class ProductThread extends Thread {// 生产者线程类
private Flower flower;
private static int id = 0; //鲜花流水号
private Random random;
public ProductThread(String name, Flower flower, long seed) {// 构造方法进行初始化
super(name);
this.flower = flower;
this.random = new Random(seed);
}
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000));// 随机休眠
String flowerId = "鲜花流水号" + nextId();
flower.put(flowerId);
}
} catch (InterruptedException e) {
System.out.println("生产者放置鲜花异常 " + e.getMessage());
}
}
public int nextId() {
return id++;
}
}
class ConsumeThread extends Thread {// 消费者线程
private final Random random;
private final Flower flower;
public ConsumeThread(String name,Flower flower,long seed){
super(name);
this.flower=flower;
this.random=new Random(seed);
}
public void run(){
try {
while(true){
String flower=this.flower.take();
Thread.sleep(random.nextInt(1000));
}
} catch (Exception e) {
System.out.println("消费者取出鲜花异常 " + e.getMessage());
}
}
}
public class TextProductAndConsume {// 用多线程模拟生产者与消费者类
public static void main(String[] args) {
Flower flower = new Flower(5);// 创建可以放置5朵鲜花的花篮
new ProductThread("ProductThread-1", flower, 001).start();
new ProductThread("ProductThread-2", flower, 002).start();
new ProductThread("ProductThread-3", flower, 003).start();
new ProductThread("ProductThread-4", flower, 004).start();
new ProductThread("ProductThread-5", flower, 005).start();
new ConsumeThread("ConsumeThread-1",flower,101).start();
new ConsumeThread("ConsumeThread-2",flower,102).start();
new ConsumeThread("ConsumeThread-2",flower,103).start();
new ConsumeThread("ConsumeThread-2",flower,104).start();
new ConsumeThread("ConsumeThread-2",flower,105).start();
}
}
源程序解读
(1)生产者线程ProductThread类在其构造方法中设置花篮和创建随机对象,run()方法根据为真的条件进行循环,每隔随机生成的毫秒后便生产一个鲜花产品,并调用put()方法将产品存入到花篮中。消费者线程ConsumeThread类在其构造方法中设置花篮和创建随机对象,run()方法根据为真条件进行循环,然后调用take()方法从花篮中取走一个鲜花产品,便再休眠随机生成的毫秒数。
(2)Flower类负责存入put与取走take产品。put()方法负责存入产品。当生产者线程在调用put()方法来存入产品时,如果发现篮中鲜花已满便生产鲜花,生产者线程进入等待状态;如果篮中鲜花未满,当向篮中放鲜花,然后调用notify()或者notifyAll()方法,唤醒等待的消费者线程。其中对于原书中把输出语句
System.out.println("当前线程" + Thread.currentThread().getName() + "放置鲜花," + flowerId);
放置在put()方法最开始是不对的,当前的生产者线程都还没有校验数组data是否有空位置就输入生产者已经放置鲜花的语句是不对的,本来可以放置5朵鲜花的花篮,最后产生了10个输出(5个正常输出,5个线程等待的非正常的输出),应该把该输出语句放置到notifyAll()方法后面。
take()方法用来取走鲜花产品。如果消费者在调用take()方法发现篮中没有鲜花,则当消费者线程进入等待状态;如果篮中有鲜花,还有可以放鲜花的地方,则在取走鲜花时,调用notify()方法,唤醒等待的生产者线程。
(3)在类的main()方法中建立一个有五朵鲜花的花篮,并为花篮关联5个生产者线程和5个消费者线程,启动这些线程,便可以模拟生产者消费者。