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

今天看了一片博文,讲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-10-20 07:30:33

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

java多线程 生产者消费者模式

package de.bvb; /** * 生产者消费者模式 * 通过 wait() 和 notify() 通信方法实现 * */ public class Test1 { public static void main(String[] args) { Godown godown = new Godown(50); for (int i = 0; i < 5; i++) { new ProducerThread(i * 10, godown).start(); new ConsumerThre

java 多线程-生产者消费者模式-管程法

生产者消费者模式管程法通过容器中介,将数据放入和取出 wait()导致当前线程等待,直到另一个线程调用该对象的notify()或notyfyAll()方法notify()唤醒正在等待对象监视器的单个线程,notifyAll()唤醒正在等待对象监视器的所有线程 public class tuble { public static void main(String[]args) { SynContainer container=new SynContainer(); new Productor(co

java 多线程生产者消费者

class Res { private String name; private int count = 1; private boolean flag; public synchronized void set(String name) { while (flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name + "--" + cou

java多线程 生产者消费者案例-虚假唤醒

package com.java.juc; public class TestProductAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Produce produce = new Produce(clerk); Consumer consumer = new Consumer(clerk); new Thread(produce, "线程A").start(); ne

[JAVA 多线程] 生产者消费者实例

正好有人问,就直接将代码记录下来. 背景:有一个仓库存储货物,存在着生产者和消费者,设计一个可以并发的实现. 设计思路:设计一个仓库类,类中保存最大的容量限制和当前的count,类中包含生产和消费的方法,并且都是synchronized. 具体代码: package com.test.tiny; public class Store { private final int MAX_SIZE; //最大 private int count; // 当前 public Store(int n) {

Java多线程-生产者/消费者模式实现

单生产者与单消费者 示例: public class ProduceConsume { public static void main(String[] args) { String lock = new String(""); Produce produce = new Produce(lock); Consume consume = new Consume(lock); new Thread(() -> { while (true) { produce.setValue();

java多线程生产者消费者

//Java Thread producer customer class ThreadTest { public static void main(String[] args) { Q q=new Q(); Producer p=new Producer(q); Customer c=new Customer(q); Thread t0=new Thread(p); Thread t1=new Thread(c); t0.start(); t1.start(); for(int i=0;i<5

Java 多线程 生产者消费者问题

1 package producer; 2 3 public class SyncStack { 4 int index =0; 5 SteamedBun[] bunArr = new SteamedBun[6]; //栈里只能放6个元素 6 7 public synchronized void push(SteamedBun bun) 8 { 9 while(index >= bunArr.length) //栈满等待 10 { 11 bun.setIndex(bunArr.length -1

Java多线程--生产者与消费者问题

说明 Java中,线程之间的通信主要是由java.lang.Object类提供的wait.notify和notifyAll这3个方法来完成: ①对象的wait方法被调用后,线程进入对象的等待队列中,并释放对象锁,其它线程可以竞争使用此对象锁:sleep方法使得一个线程进入睡眠状态,但是线程所占有的资源并没有释放. ②当对象的notify方法被调用,该方法会从对象的等待队列中随机取出一个线程来唤醒:notifyAll是唤醒等待队列中所有线程,这些线程会与其它正在执行的线程共同竞争对象锁. ③wai