模拟生产者、消费者
- 一个生产者、一个消费者、仓库容量为1,模拟生产者、消费者通信过程。
- 思路:
仓库:ArrayList,初始化大小为1。
生产者:当仓库未满的情况下,进行生产,并通知消费者消费;当仓库是满的时候,等待消费者消费后的通知。
消费者:当仓库中不为空的情况下,进行消费,并通知生产者生产;当仓库为空时,等待生产者生产后的通知。
- 实现:
public class ProducerConsumer0416 {
//生产者
static class Producer implements Runnable{
ArrayList<Integer> arrayList;//仓库
int NUM = 20;//生产的次数
public Producer(ArrayList<Integer> arrayList){
this.arrayList = arrayList;
}
@Override
public void run() {
while (NUM -- > 0) {
synchronized (arrayList){
//判断仓库是否满了,指定仓库大小为1,所以这里只用给定大小
if (arrayList.size() < 1){//未满,生产
arrayList.add(1);
System.out.println("正在生产");
arrayList.notify();//通知消费者消费
}else {
try {
arrayList.wait();//满了,则等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//消费者
static class Consumer implements Runnable {
ArrayList<Integer> arrayList;
int NUM = 20;
public Consumer(ArrayList<Integer> arrayList){
this.arrayList = arrayList;
}
@Override
public void run() {
while (NUM -- > 0) {
synchronized (arrayList){
//判断仓库中是否有产品
if (arrayList.size() > 0){
arrayList.remove(0);//有产品,则进行消费
System.out.println("正在消费");
arrayList.notify();//通知生产者生产
}else {
try {
arrayList.wait();//空则等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>(1);//仓库
new Thread(new Producer(arrayList)).start();
new Thread(new Consumer(arrayList)).start();
}
}
- 实现2:
一个容器用来作为仓库,仓库用queue队列,来实现;
仓库大小:1;
public class ProducerConsumer0416 {
//生产者
static class Producer1 implements Runnable{
Container0416 container;
int NUM = 20;
public Producer1(Container0416 container){
this.container = container;
}
@Override
public void run() {
while (NUM -- > 0) {
container.produce();
}
}
}
//消费者
static class Consumer1 implements Runnable {
Container0416 container;
int NUM = 20;
public Consumer1(Container0416 container){
this.container = container;
}
@Override
public void run() {
while (NUM -- > 0) {
container.consume();
}
}
}
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
Container0416 container0416 = new Container0416(queue);
new Thread(new Producer1(container0416)).start();
new Thread(new Consumer1(container0416)).start();
}
}
public class Container0416 {
private Queue<Integer> queue;
public Container0416(Queue<Integer> queue){
this.queue = new LinkedList<>();
}
public synchronized void produce(){
if (queue.size() < 1){
queue.add(1);
System.out.println("正在生产");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consume(){
if (queue.size() > 0){
queue.remove();
System.out.println("正在消费");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 同时遇到的问题:程序抛出下面的异常:java.lang.IllegalMonitorStateException。
总结了以下几点原因:
- 当synchronized修饰的对象是this时,即消费者和生产者中使用的synchronized修饰的都是this(synchronized(this)),程序会抛出不合法的监视器状态异常,因为修饰的this对象不是同一个,加锁的对象是不一致的;
- 当调用notify() 和wait()方法时,要对象.wait(),这样使用时就不会出现锁对象不一致而抛出异常;在arrayList.notify()方法调用时使用同步上下文,改为如下:
synchronized (t) {
arraylist.notify();
}
《java编程思想》第四版一书中有描述到:
“线程操作的wait()、notify()、notifyAll()方法只能在同步控制方法或同步控制块内调用。如果在非同步控制方法或控制块里调用,程序能通过编译,但运行的时候,将得到 IllegalMonitorStateException 异常,并伴随着一些含糊信息,比如 ‘当前线程不是拥有者’。其实异常的含义是 调用wait()、notify()、notifyAll()的任务在调用这些方法前必须 ‘拥有’(获取)对象的锁。”同样在JAVA JDK API文档中也有描述如下:
wait()、notify()、notifyAll()法只应由作为此对象监视器的所有者的线程来调用。
通过以下三种方法之一,线程可以成为此对象监视器的所有者:
通过执行此对象的同步 (Sychronized) 实例方法。
通过执行在此对象上进行同步的 synchronized 语句的正文。
对于 Class 类型的对象,可以通过执行该类的同步静态方法。
- 生产者消费者模型实现2时,因为把queue作为仓库,所以在消费者消费时,我调用了queue.peek()方法,同时遇到了生产者只生产一次,消费者可以持续消费的问题,如图
在解决问题的步骤:查看peek()方法的实现,因为queue是LinkedList实例化出的对象,查看LinkedList类下的peek(),相当于这里的是获取操作,相当于get(),而不是将元素删除,所以之后调用了remove()方法来实现:
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}