java中 wait notify实现生产者-消费者问题

本文介绍了多线程环境下生产者与消费者问题的实现,包括单生产者单消费者、单生产者多消费者以及多生产者多消费者的场景。详细解释了如何通过同步方法和等待/通知机制确保数据的正确流动,并讨论了在单生产者多消费者场景下可能出现的死锁问题及其解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

只有一个商品的情况

1.一生产者一消费者

首先定义仓库类MyStack,用来存储商品

package com.feng.example;

import java.util.ArrayList;
import java.util.List;
/**
 * 生产者与消费者问题的仓库
 * 要求仓库只有一件商品
 * @author feng
 *
 */
public class MyStack {
	
	private List<String> list = new ArrayList<String>();
	
	synchronized public void pop()
	{
		try {
			
			if(list.size() == 0)
			{
				this.wait();  //这里使用this,如果使用list.wait()需要使用synchronized语句对list加锁
			}
			System.out.println("销售商品");
			list.remove(0);
			this.notify();
		}
		 catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
	
	synchronized public void push()
	{
		try {
			
			if(list.size() != 0)
			{
				this.wait();
			}
			System.out.println("生产商品");
			list.add("商品");
			this.notify();
		}catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

}

定义生产者类Producer

package com.feng.example;

public class Producer extends Thread{

	private MyStack myStack;
	
	public Producer(MyStack myStack)
	{
		this.myStack = myStack;
	}

	@Override
	public void run() {
		
		while(true)
		{
			myStack.push();
		}
	}
	
	
}

定义消费者类:

package com.feng.example;

public class Customer extends Thread{
	
	private MyStack myStack;
	
	public  Customer(MyStack myStack)
	{
		this.myStack = myStack;
	}
	
	public void run()
	{
		while(true)
		{
			myStack.pop();
		}
	}

}

定义测试类:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */

	
	public static void main(String[] args) {
			
		MyStack myStack = new MyStack();
		
		Thread producer = new Producer(myStack);
		Thread customer = new Customer(myStack);
		
		producer.start();
		customer.start();
		
	}

}

分析:在操作栈MyStack中,push和pop方法都是synchronized方法,也就是线程producer和customer同一时刻只会有一个线程去执行操作栈,使用wait,notify也保证了push总是在pop前执行,因此实验结果是生产商品,出售商品交替打印。

运行结果:

生产商品
销售商品
生产商品
销售商品
生产商品
销售商品
生产商品
销售商品
生产商品
销售商品

2.一生产者多消费者

修改测试类代码,将测试类代码修改为一个生产者,5个消费者的情况

修改代码如下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */

	
	public static void main(String[] args) {
			
		MyStack myStack = new MyStack();
		
		Thread producer = new Producer(myStack);
		
		Thread customer1 = new Customer(myStack);
		Thread customer2 = new Customer(myStack);
		Thread customer3 = new Customer(myStack);
		Thread customer4 = new Customer(myStack);
		Thread customer5 = new Customer(myStack);
		
		customer1.start();
		customer2.start();
		customer3.start();
		customer4.start();
		customer5.start();
		
		producer.start();
		
	}

}

运行程序,先查看运行结果:

生产商品
销售商品
生产商品
销售商品
销售商品
销售商品
Exception in thread "Thread-5" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.RangeCheck(ArrayList.java:547)
	at java.util.ArrayList.remove(ArrayList.java:387)
	at com.feng.example.MyStack.pop(MyStack.java:24)
	at com.feng.example.Customer.run(Customer.java:16)
Exception in thread "Thread-4" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.RangeCheck(ArrayList.java:547)
	at java.util.ArrayList.remove(ArrayList.java:387)
	at com.feng.example.MyStack.pop(MyStack.java:24)
	at com.feng.example.Customer.run(Customer.java:16)

分析:从结果中可以看出,连续销售了三件商品,但是我们的仓库只能放一件商品,连续销售三件商品肯定是不现实的。引起这个问题的原因重要在于:唤醒了同类进程。也就是说消费者本应该唤醒生产者线程,却唤醒的是消费者线程。所以会导致程序的错误。因此我们需要在每次唤醒后还要判断一次是否满足了操作的条件。因此我们应该讲MyStack类中的if改为while,修改程序如下:

package com.feng.example;

import java.util.ArrayList;
import java.util.List;
/**
 * 生产者与消费者问题的仓库
 * 要求仓库只有一件商品
 * @author feng
 *
 */
public class MyStack {
	
	private List<String> list = new ArrayList<String>();
	
	synchronized public void pop()
	{
		try {
			
			while(list.size() == 0)
			{
				this.wait();  //这里使用this,如果使用list.wait()需要使用synchronized语句对list加锁
			}
			System.out.println("销售商品");
			list.remove(0);
			this.notify();
		}
		 catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
	
	synchronized public void push()
	{
		try {
			
			while(list.size() != 0)
			{
				this.wait();
			}
			System.out.println("生产商品");
			list.add("商品");
			this.notify();
		}catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

}

运行程序,查看结果:

生产商品
销售商品   //程序应经不输出了,然而我们的代码是while(true),不输出肯定存在的问题

从输出结果中可以看出,我们的程序还存在死锁的问题,死锁的问题是怎么解决的。 描述这类问题可能出现的情况,现在有五个消费者线程,customer1,customer2,customer3,customer4,customer5假如现在5个线程都走到while(list.size() ==0),五个线程都wait阻塞。生产者线程producor,生产一个商品,执行notify操作,随机唤醒一个消费者线程(执行完notify操作后,由于是一个无限循环,producor线程在while(list.size != 0)处执行了wait阻塞)假设唤醒了customer1,customer1销售一个商品。本应该唤醒producor线程的,缺随机唤醒了一个个消费者线程假如是customer2线程,customer2线程被唤醒之后重新执行while(list.size == 0 ),由于条件成立,再次wait阻塞,现在所有的线程都处于阻塞状态,也就是造成了死锁。

解决方案就是将notify操作改为notifyAll,这在效率上肯定有低,后期再讲解怎么才能高效。

修改MyStack类:

package com.feng.example;

import java.util.ArrayList;
import java.util.List;
/**
 * 生产者与消费者问题的仓库
 * 要求仓库只有一件商品
 * @author feng
 *
 */
public class MyStack {
	
	private List<String> list = new ArrayList<String>();
	
	synchronized public void pop()
	{
		try {
			
			while(list.size() == 0)
			{
				this.wait();  //这里使用this,如果使用list.wait()需要使用synchronized语句对list加锁
			}
			System.out.println("销售商品");
			list.remove(0);
			this.notifyAll();
		}
		 catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
	
	synchronized public void push()
	{
		try {
			
			while(list.size() != 0)
			{
				this.wait();
			}
			System.out.println("生产商品");
			list.add("商品");
			this.notifyAll();
		}catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

}

运行结果:程序应经能够正常运行了

生产商品
销售商品
生产商品
销售商品
生产商品
销售商品

3.多生产者一消费者(同上)

4.多生产者多消费者(同上)

对于仓库中可以存放多件商品的问题,只需修改push的压栈条件即可,也就是知道什么时候为满,比如定义仓库最多可以放n件商品,只需修改判断条件为:while(list.size() >n){ this.wait(); }即可

转载于:https://my.oschina.net/u/2309504/blog/544086

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值