Java多线程之生产者消费者问题<一>:使用synchronized keyword解决生产者消费者问题

今天看了一片博文,讲Java多线程之线程的协作,当中作者用程序实例说明了生产者和消费者问题,但我及其它读者发现程序多跑几次还是会出现死锁,百度搜了下大都数的样例也都存在bug,经过细致研究发现当中的问题。并攻克了,感觉有意义贴出来分享下。

以下首先贴出的是有bug的代码,一个4个类。Plate.java:

package CreatorAndConsumer;

import java.util.ArrayList;
import java.util.List;

/**
 * 盘子,表示共享的资源
 * @author Martin
 *
 */
public class Plate {
	private List<Object> eggs = new ArrayList<Object>();

	/**
	 * 获取蛋
	 * @return
	 */
	public Object getEgg()
	{
		System.out.println("消费者取蛋");
		Object egg = eggs.get(0);
		eggs.remove(0);
		return egg;
	}

	/**
	 * 增加蛋
	 * @return
	 */
	public void addEgg(Object egg)
	{
		System.out.println("生产者生蛋");
		eggs.add(egg);
	}

	/**
	 * 获取蛋个数
	 * @return
	 */
	public int getEggNum()
	{
		return eggs.size();
	}
}

消费者类:Consumer2.java

package CreatorAndConsumer;

public class Consumer2 implements Runnable {
	/**
	 * 线程资源
	 */
	private Plate plate;

	public Consumer2(Plate plate) {
		this.plate = plate;
	}

	@Override
	public void run() {
		synchronized (plate) {
			// 假设此时蛋的个数大于0。则等等
			while (plate.getEggNum() < 1) {
				try {
					// 这个细节须要注意,假设线程进入wait,那么其上的锁就会临时得到释放。
					// 不然其它线程也不能进行加锁,然后唤醒本线程
					plate.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			// 唤醒后。再次得到资源锁。且条件满足就能够放心地取蛋了
			plate.getEgg();
			plate.notify();

		}
	}
}

生产者类:Creator2.java

package CreatorAndConsumer;

/**
 * 生产者
 *
 * @author Martin
 *
 */
public class Creator2 implements Runnable {
	/**
	 * 线程资源
	 */
	private Plate plate;

	public Creator2(Plate plate) {
		this.plate = plate;
	}

	@Override
	public void run() {
		synchronized (plate) {
			// 假设此时蛋的个数大于0,则等等
			while (plate.getEggNum() >= 5) {
				try {
					// 这个细节须要注意,假设线程进入wait。那么其上的锁就会临时得到释放。
					// 不然其它线程也不能进行加锁。然后唤醒本线程
					plate.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			// 唤醒后,再次得到资源锁,且条件满足就能够放心地生蛋啦
			Object egg = new Object();
			plate.addEgg(egg);
			plate.notify();
		}
	}
}

測试类:Tester.java

package CreatorAndConsumer;

public class Tester {
	public static void main(String[] args)
	{
		//共享资源
		Plate plate = new Plate();

		//加入生产者和消费者
		for(int i = 0 ; i < 100; i ++)
		{
			//有bug版
			new Thread(new Creator2(plate)).start();
			new Thread(new Consumer2(plate)).start();

			//无bug版
			//new Thread(new Creator(plate)).start();
			//new Thread(new Consumer(plate)).start();
		}
	}
}

假设多执行几次或者将測试类中的循环次数改大,则会发现出现死锁的概率还是非常高的。以下分析发生这样的问题的解决办法:

在jdk中对于Object.wait有这种一段解释:当前线程必须拥有此对象监视器。该线程放弃对此监视器的全部权并等待,直到其它线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。

可见以下一种情况就可能出现:消费者1进入等待状态(此时资源锁已被放开),假设此时消费者2获取到了资源同步锁(没有人保证消费者1进入等待,下一个拿到锁的一定是生产者)。消费者2推断没有资源也进入等待状态。此时生产者1生产了,并notify了消费者1,消费者1顺利地消费了,并运行notify操作,但此时消费者2却也由于资源而处于等待状态。从而唤醒了消费者2(消费者1本欲唤醒其它生产者)。而此时并没有不论什么资源。导致了整个程序由于消费者2陷入无限的等待。形成了死锁。

经过以上分析。究其根本原因是:同一时候几个消费者或几个生产者处于等待状态,导致消费者可能唤醒的还是消费者,或者生产者唤醒的还是生产者。那么假设我们可以保证同一时候仅仅有一个消费者处于wait状态(生产者同理),那就就能保证消费者唤醒的一定是生产者,从而能使整个任务顺利进行下去。以下是改动后的代码:

改进后的Consumer.java

package CreatorAndConsumer;

public class Consumer implements Runnable {
	/**
	 * 线程资源
	 */
	private Plate plate;

	/**
	 * 生产者锁:用于锁定同一时间仅仅能有一个生产者进入生产临界区(假设同一时候又两个生产者进入临界区。那么非常有可能当中一个生产者本想唤醒消费者却唤醒了生产者)
	 */
	private static Object consumerLocker = new Object();

	public Consumer(Plate plate) {
		this.plate = plate;
	}

	@Override
	public void run() {
		// 必须先获得生产者锁才干生产
		synchronized (consumerLocker) {
			synchronized (plate) {
				// 假设此时蛋的个数大于0,则等等
				while (plate.getEggNum() < 1) {
					try {
						// 这个细节须要注意,假设线程进入wait,那么其上的锁就会临时得到释放。
						// 不然其它线程也不能进行加锁,然后唤醒本线程
						plate.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

				// 唤醒后,再次得到资源锁,且条件满足就能够放心地取蛋了
				plate.getEgg();
				plate.notify();

			}
		}
	}
}

改进后的Creator.java:

package CreatorAndConsumer;

/**
 * 生产者
 *
 * @author Martin
 *
 */
public class Creator implements Runnable {
	/**
	 * 线程资源
	 */
	private Plate plate;

	/**
	 * 生产者锁:用于锁定同一时间仅仅能有一个生产者进入生产临界区(假设同一时候又两个生产者进入临界区。那么非常有可能当中一个生产者本想唤醒消费者却唤醒了生产者)
	 */
	private static Object creatorLocker = new Object();

	public Creator(Plate plate) {
		this.plate = plate;
	}

	@Override
	public void run() {
		//必须先获得生产者锁才干生产
		synchronized (creatorLocker) {
			synchronized (plate) {
				// 假设此时蛋的个数大于0,则等等
				while (plate.getEggNum() >= 5) {
					try {
						// 这个细节须要注意。假设线程进入wait,那么其上的锁就会临时得到释放。
						// 不然其它线程也不能进行加锁,然后唤醒本线程
						plate.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

				// 唤醒后,再次得到资源锁,且条件满足就能够放心地生蛋啦
				Object egg = new Object();
				plate.addEgg(egg);
				plate.notify();
			}
		}
	}
}

改进说明:改进后的生产者和消费者分别加了生产者锁和消费者锁。分别用于锁定同一时间仅仅能有一个消费者(生产者)进入生产临界区(假设同一时候又两个生产者进入临界区,那么非常有可能当中一个生产者本想唤醒消费者却唤醒了生产者),总的来说就是一共三个锁:消费者锁、生产者锁、生产者和消费者共享的锁。

终于,写多线程的时候须要注意的是一个资源可能唤醒的是全部因该资源而等待的线程,因此消费者线程不一定唤醒的就是生产者线程也可能是消费者线程。

时间: 2024-12-26 20:16:30

Java多线程之生产者消费者问题&lt;一&gt;:使用synchronized keyword解决生产者消费者问题的相关文章

java多线程并发(一)——Semaphore,volatile,synchronized ,Lock

在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题.我们先看具体看一下这三个概念: 1.原子性 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行. 一个很经典的例子就是银行账户转账问题 2.可见性 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值. 3.有序性 有序性:即程序执行的顺序按照代码的先后顺序执行. Semaphore 简介 信号量(Semaphore),有时被称为信号

Java 多线程, 同步访问, 线程锁,锁对象,ReentrantLock,synchronized

1.为什么要同步访问数据? 当两个或以上的线程需要共享对同一数据的存取,可能会发生共享数据的讹误. 2.实现同步的方式 2.1 ReentrantLock类 School类: class School{ private int stuNum; private Lock lock; private Condition condition; public School(int stuNum) { this.stuNum = stuNum; lock = new ReentrantLock(); co

synchronized与static synchronized 的差别、synchronized在JVM底层的实现原理及Java多线程锁理解

本Blog分为例如以下部分: 第一部分:synchronized与static synchronized 的差别 第二部分:JVM底层又是怎样实现synchronized的 第三部分:Java多线程锁,源码剖析 第一部分:synchronized与static synchronized的差别 1.synchronized与static synchronized 的差别 synchronized是对类的当前实例进行加锁,防止其它线程同一时候訪问该类的该实例的全部synchronized块.注意这里

java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

 *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时候才能消费,仓空则等待. *3.当消费者发现仓储没有产品可消费的时候,会唤醒等待生产者生产. *4.生产者在生产出可以消费的产品的时候,应该通知等待的消费者去消费. 下面先介绍个简单的生产者消费者例子:本例只适用于两个线程,一个线程生产,一个线程负责消费. 生产一个资源,就得消费一个资源. 代码如下: pub

Java多线程:生产者消费者更佳的解决方法(确定不会出现死锁)

今天看了一片博文,讲Java多线程之线程的协作,其中作者用程序实例说明了生产者和消费者问题,但我及其他读者发现程序多跑几次还是会出现死锁,百度搜了下大都数的例子也都存在bug,经过仔细研究发现其中的问题,并解决了,感觉有意义贴出来分享下. 下面首先贴出的是有bug的代码,一个4个类,Plate.java: package CreatorAndConsumer; import java.util.ArrayList; import java.util.List; /** * 盘子,表示共享的资源

Java多线程实现生产者消费者延伸问题

在操作系统中有一类问题被称为生产者消费者问题:意为,有数个生产者生产产品,有数个消费者消费产品,他们共享一定数量的缓存. 这里用java多线程编程,实现生产者消费者问题的一种延伸,橘子苹果问题. 题目如下: 有苹果橘子生产者各20个,有苹果橘子消费者各20个,他们公用20个缓存区.要求能随时查看缓存区内容,随时查看生产消费内容情况,随时暂停生产开始生产. 我们的实现思路: 1.首先创建一个缓存区类,其中包含静态的,长度大小为20的数组,用来存放和取出生产的产品:一个静态的日志变量List,用来记

Java 多线程学习笔记:生产者消费者问题

前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章<阻塞队列实现生产者消费者模式>.在文中,使用的是Java的concurrent包中的阻塞队列来实现.在看完后,自行实现阻塞队列. (一)准备 在多线程中,生产者-消费者问题是一个经典的多线程同步问题.简单来说就是有两种线程(在这里也可以做进程理解)——生产者和消费者,他们共享一个固定大小的缓存区(如一个队列).生产者负责产生放入新数据,消费者负责取出缓存区的数据.具体介绍请参考 Producer-consumer

JAVA多线程之生产者消费者

生产者消费者并发编程: 假设仓库有10个仓位,分别有10个生产者和10个消费者,生产者不断生产产品,放入仓库的仓位中,而消费者则不断从仓库中获取产品, 如果仓库已满,则生产者要等待,等消费者消费后,空出仓位后,再继续放入产品. 反之如果仓库已空,则消费者要等待,等待生产者生产出产品后,再继续消费产品. 关于生产者.消费者有四种实现方式 1,wait,nofity方式 2,ReentrantLock锁的await()和signal() 3,阻塞队列的方式 4,Semaphore 信号量方式 下面分

java 多线程并发系列之 生产者消费者模式的两种实现

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题.该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度. 为什么要使用生产者和消费者模式 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程.在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据.同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者.为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式. 什么是生