文章目录

  • 前言
  • 一、什么是消费者/生产者问题?
  • 二、问题分析
  • 三、问题解决
  • 总结



前言

本文旨在介绍如何使用代码简单的解决生产者/消费者问题,涉及到的知识点只有多线程的创建和同步方法以及wait()和notify()方法,适合初学Java多线程的人,目的是为了帮助初学者更好的理解这个问题如何用代码实现,如果想要更深入的了解相关问题,请移步其他文章


一、什么是消费者/生产者问题?

  生产者(Productor)的任务是生产产品并将产品交给店员(Clerk),而消费者(Customer)的任务则是从店员处购买产品,店员一次只能持有固定数量的产品(比如:20),如果达到指定数量,店员会阻止生产者继续生产,如果店中有空位了再通知生产者继续生产;如果店中没有产品了,店员会通知消费者停止购买,如果店中有产品了再通知消费者继续购买产品。

二、问题分析

  1.该问题是否是一个多线程问题
  答:明显是的,因为在买卖商品这个进程中,有生产者线程和消费者线程在同时进行
  2.多线程可能会出现线程安全问题,那么该问题中是否有线程安全问题,如果有,那么哪里存在?
  答:存在多线程问题,生产者线程和消费者线程都共享同一个数据——产品数量,因为两个线程是同时进行的,所以有可能出现在生产者把产品交给店员的时候,消费者已经购买了很多产品的情况,这就导致店员输出的产品数量与它从生产者那里拿到产品的行为不一致(正常来说店员从生产商那里拿到产品,产品数量应该增加,但在这个过程中消费者如果又购买了很多产品,那么就导致店员从生产商那里拿到产品后显得产品数少了很多,导致不同步)
  3.是否涉及线程通信问题?
  答:是,当产品数量小于0时,需要生产者线程来进行,此时需要消费者线程进入阻塞状态,当产品数量大于等于20时,需要消费者线程来进行,此时需要生产者进程进入阻塞状态

三、问题解决

1.首先这个问题中一共有三个对象,生产者、消费者和店员,相应的创建三个类,其中生产者类和消费者类都要实现多线程,这里我们使用继承Thread类的方法实现多线程
实现代码如下:

public class ThreadTest{
}
class Clerk{
}
class Producer extends Thread{
}
class Consumer extends Thread{
}

2.产品的进货和销售都需要同一个店员来管理,其他店员的话不知道进货销售情况,所以两个类都需要一个Clerk类的对象,并提供相应的构造方法
实现代码如下:

public class ThreadTest{
}
class Clerk{
}
class Producer extends Thread{
	private Clerk clerk;
	Producer(){
	}
	Producer(Clerk clerk){
		this.clerk = clerk;
	}
}
class Consumer extends Thread{
private Clerk clerk;
	Consumer(){
	}
	Consumer(Clerk clerk){
		this.clerk = clerk;
	}
}

3.然后这两个类要实现多继承除了继承Thread类,还要重写Thread类的run()方法,生产者的run()方法主要是:1.生产产品2.把产品交给店员(店员取走产品),消费者的run()方法主要是:1.选购产品2.拿走产品(店员卖给消费者)
实现代码如下:

public class ThreadTest{
}
class Clerk{
}
class Producer extends Thread{
	private Clerk clerk;
	Producer(){
	}
	Producer(Clerk clerk){
		this.clerk = clerk;
	}
	@Override
	public void run(){
		while(true){
			System.out.println(getName() + "开始生产产品");
			//这里后面会写店员取走产品的方法
		}
	}
}
class Consumer extends Thread{
private Clerk clerk;
	Consumer(){
	}
	Consumer(Clerk clerk){
		this.clerk = clerk;
	}
	@Override
	public void run(){
		while(true){
			System.out.println(getName() + "开始选购产品");
			//这里后面会写店员卖出产品的方法
		}
	}
}

4.接下来我们再看店员类,首先店员肯定是有一个产品数量的属性,这也是出现线程安全的地方,其次店员还要有从生产者那里拿到产品(getProduct)把产品卖给消费者(sellProduct) 的方法,并且产品数量如果大于20则不拿货,等于0则不卖货,有了这两个方法之后便可以让生产者和消费者调用相应的方法了
实现代码如下:

public class ThreadTest{
}
class Clerk{
	private int product = 0;
	public void getProduct(){
		if(product < 20){
			product++;
			System.out.println("从" + Thread.currentThread().getName() + "那里拿到了1个商品,目前还剩" + product + "个");
		}else{
		//这里后面会写让生产者停止生产的语句
		}
	}
	public void sellProduct(){
		if(product > 0){
			//注意这里要用print,与下面的输出语句衔接
			System.out.print(Thread.currentThread().getName() + "买走了商品" + product);
			product--;
			System.out.println(",还剩" + product + "个商品");
		}else{
		//这里后面会写让消费者停止购买的语句
		}

	}
}
class Producer extends Thread{
	private Clerk clerk;
	Producer(){
	}
	Producer(Clerk clerk){
		this.clerk = clerk;
	}
	@Override
	public void run(){
		while(true){
			System.out.println(getName() + "开始生产产品");
			clerk.getProduct();
		}
	}
}
class Consumer extends Thread{
private Clerk clerk;
	Consumer(){
	}
	Consumer(Clerk clerk){
		this.clerk = clerk;
	}
	@Override
	public void run(){
		while(true){
			System.out.println(getName() + "开始选购产品");
			clerk.sellProduct();
		}
	}
}

5.接下来让我们看一下线程安全问题,操作同步数据的地方分别是在Clerk类中的getProduct()方法和sellProduct()方法中,所以需要在这里加上同步监视器(锁),由于全部操作都是在该方法中,所以直接声明这两个方法为同步方法,此时这两个方法都共享同一把锁也就是调用该方法的对象——Clerk类对象,因为我们只会创建一个Clerk类的对象,所以锁也只有一个。但此时仍可能出现问题,假如生产者线程一直抢到clerk.getProduct()方法的执行权,那么当product值大于20时,仍会接着生产,所以我们需要在两处方法的else处加上wait()方法使得该方法满足一定条件后进入阻塞状态,直到被唤醒
实现代码如下:

public class ThreadTest{
}
class Clerk{
	private int product = 0;
	public synchronized void getProduct(){
		if(product < 20){
			product++;
			System.out.println("从" + Thread.currentThread().getName() + "那里拿到了1个商品,目前还剩" + product + "个");
		}else{
			try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
		}
	}
	public synchronized void sellProduct(){
		if(product > 0){
			//注意这里要用print,与下面的输出语句衔接
			System.out.print(Thread.currentThread().getName() + "买走了商品" + product);
			product--;
			System.out.println(",还剩" + product + "个商品");
		}else{
			try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
		}

	}
}
class Producer extends Thread{
	private Clerk clerk;
	Producer(){
	}
	Producer(Clerk clerk){
		this.clerk = clerk;
	}
	@Override
	public void run(){
		while(true){
			System.out.println(getName() + "开始生产产品");
			clerk.getProduct();
		}
	}
}
class Consumer extends Thread{
private Clerk clerk;
	Consumer(){
	}
	Consumer(Clerk clerk){
		this.clerk = clerk;
	}
	@Override
	public void run(){
		while(true){
			System.out.println(getName() + "开始选购产品");
			clerk.sellProduct();
		}
	}
}

6.最后一步就是选择两个线程被唤醒的时机,首先看生产者线程,它会在product的值>=20时进入阻塞状态,那么只要product<20,那么就可以唤醒,消费者线程会在product的值为0时进入阻塞状态,那么只要等product的值>0时就可以被唤醒,也就是分别在产品数量++和–时用notify()方法唤醒,为了更清楚的显示可在调用方法前加上sleep方法指定合适阻塞时间,最后在public ThreadTest类中的main方法里创建相应对象,调用相应方法即可解决该问题
实现代码如下:

public class ThreadTest{
	public static void main(String []args){
	//创建店员对象
	Clerk clerk = new Clerk();
	//创建生产者线程和消费者线程
	Producer producer = new Producer(clerk);
	Consumer consumer = new Consumer(clerk);
	//更改线程名
	producer.setName("生产者");
	consumer.setName("消费者");
	//启动线程
	producer.start();
	consumer.start();
	}
}
class Clerk{
	private int product = 0;
	public synchronized void getProduct(){
		if(product < 20){
			product++;
			notify();
			System.out.println("从" + Thread.currentThread().getName() + "那里拿到了1个商品,目前还剩" + product + "个");
		}else{
			try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
		}
	}
	public synchronized void sellProduct(){
		if(product > 0){
			//注意这里要用print,与下面的输出语句衔接
			System.out.print(Thread.currentThread().getName() + "买走了商品" + product);
			product--;
			notify();
			System.out.println(",还剩" + product + "个商品");
		}else{
			try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
		}

	}
}
class Producer extends Thread{
	private Clerk clerk;
	Producer(){
	}
	Producer(Clerk clerk){
		this.clerk = clerk;
	}
	@Override
	public void run(){
		while(true){
			try {
                sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
			System.out.println(getName() + "开始生产产品");
			clerk.getProduct();
		}
	}
}
class Consumer extends Thread{
private Clerk clerk;
	Consumer(){
	}
	Consumer(Clerk clerk){
		this.clerk = clerk;
	}
	@Override
	public void run(){
		while(true){
			try {
                sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
			System.out.println(getName() + "开始选购产品");
			clerk.sellProduct();
		}
	}
}

补充:可以通过控制sleep的时间以及创建多个生产者或者消费者对象控制生产者的生产速度和消费者的购买速度

总结

本文相对全面的讲了生产者/消费者问题,手把手的从问题分析,到代码实践的角度一步步走到最后,如有问题,欢迎指正,希望对各位有所帮助