JAVA 多线程经典案例-生产者消费者模型【使用wait/notify实现】
- 生产者消费者模型实现细节
- 功能实现说明
- 代码实现
- 写在最后的话
生产者消费者模型实现细节
生产者生产产品到公共仓库,消费者消费公共仓库中产品。
情况一:当公共仓库产品达到仓库容量上限,生产者停止生产;
情况二:当公共仓库没有产品,或达到设置的仓库容量下限,消费者停止消费;
情况三:当公共仓库产品达到容量上限时,消费者消费一次,就可以让生产者继续生产;
情况四:当公共仓库没有产品时,生产者生产一次,就可以让消费者继续消费;
功能实现说明
- 公共仓库 ,包含“仓库容量上限”、“仓库容量下限”、“当前容量”基础属性,及“入库”、“出库”基础方法;
- 生产者 ,包含“所属仓库”、“每次生产数量”基础属性,及“生产存入仓库”基础方法;
- 消费者 ,包含“所属仓库”、“每次消费数量”基础属性,及“仓库取出消费”基础方法;
代码实现
- 公共仓库 ,抽取“入库”、“出库”基础方法作为接口,后续可通过实现该接口,重写方法来实现;
(1)公共仓库接口
/**
* @author Evan
* @date 2018年12月11日
* @method putIn(int num) //往仓库中存入指定数量的产品
* @method outOf(int num) //从仓库中取出制定数量的产品
*/
interface AbstractStorage {
void putIn(int num);
void outOf(int num);
}
(2)仓库实现类
仓库类只需判断库存量是否达到存入/取出阈值,从而限制生产者/消费者的存入/取出,不需要使用循环来判断;
/**
* @author Evan
* @date 2018年12月11日
* @param MAX_PRODUCT //仓库容量上限
* @param MIN_PRODUCT //仓库容量下限
* @param stock //仓库当前容量,属于共享资源
* @method putIn(int) //当(库存+即将存入)大于容量上限,停止存入
* @method outOf(int) //当(库存-即将取出)小于容量上限,停止取出
*/
class Storage implements AbstractStorage {
public final int MAX_PRODUCT = 10;
public final int MIN_PRODUCT = 0;
private int stock;
@Override
public synchronized void putIn(int num) {
if ((stock + num) >= MAX_PRODUCT) {
System.out.println("库存已满!");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
stock += num;
System.out.println(Thread.currentThread().getName() + "存入,库存:" + stock);
notifyAll();
}
}
@Override
public synchronized void outOf(int num) {
if ((stock - num) <= MIN_PRODUCT) {
System.out.println("库存已空!");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
stock -= num;
System.out.println(Thread.currentThread().getName() + "取出,库存:" + stock);
notifyAll();
}
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
}
- 生产者
使用实现Runnable接口方式创建生产者线程,将生产者生产操作放到重写的run()方法中,实现生产者线程生产;
/**
* @author Evan
* @Data 2018年12月11日
* @param storage //公共仓库
* @param num //生产数量
* @constructor Produce(AbstractStorage) //构造器
* @method prod(int) //生产者生产方法
* @method run() //生产者线程执行方法
* @method setNum(int) //设置生产者每次生产数量
*/
class Producer implements Runnable {
private AbstractStorage storage;
private int num;
public Producer(AbstractStorage storage) {
this.storage = storage;
}
@Override
public void run() {
prod(num);
}
private void prod(int num) {
while (true) {
try {
Thread.sleep(100); //需要添加延时,不然生产者线程获得执行权时,一瞬间即可生产到仓库上限值
} catch (InterruptedException e) {
e.printStackTrace();
}
storage.putIn(num);
}
}
public void setNum(int num) {
this.num = num;
}
}
- 消费者
使用实现Runnable接口方式创建消费者线程,将消费者消费操作放到重写的run()方法中,实现消费者线程消费;
/**
* @author Evan
* @Data 2018年12月11日
* @param storage //公共仓库
* @param num //生产数量
* @constructor Consume(AbstractStorage) //构造器
* @method cons(int) //消费者生产方法
* @method run() //消费者线程执行方法
* @method setNum(int) //设置消费者每次生产数量
*/
class Consumer implements Runnable {
private AbstractStorage storage;
private int num;
public Consumer(AbstractStorage storage) {
this.storage = storage;
}
@Override
public void run() {
cons(num);
}
private void cons(int num) {
while (true) {
try {
Thread.sleep(100); //需要添加延时,不然消费者线程获得执行权时,一瞬间即可消费到仓库下限值
} catch (InterruptedException e) {
e.printStackTrace();
}
storage.outOf(num);
}
}
public void setNum(int num) {
this.num = num;
}
- 测试类
/**
* @author Evan
* @Date 2018年12月11日
* @param storage //创建公共仓库
* @param producer //创建生产者线程
* @param consumer //创建消费者线程
*/
public class TestA {
public static void main(String[] args) {
AbstractStorage storage = new Storage();
Producer producer = new Producer(storage);
Consumer consumer = new Consumer(storage);
producer.setNum(2);
consumer.setNum(1);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.setName("producer");
t2.setName("consumer");
t1.start();
t2.start();
}
}
测试输出
写在最后的话
使用wait/notify方法实现,很多时候会出现一些奇怪的问题,例如:
1、两者都持有锁且等待对方释放锁,造成死锁的情况;
2、对未持有锁的对象进行释放锁,导致IllegalMonitorStateException异常;
3、加锁时,双方所加的锁并非同一把锁,导致数据异常;
关于生产者/消费者模型,现在更多是使用阻塞队列(Queue)的方式来实现,后续会接着去学习那一块知识。
【注】以上为本人通过网上视频教程以及实例代码整理所得,如果出错以及更好的提议,欢迎留言指正,一起学习。