多线程中多生产多消费问题

一、多生产多消费实例

多线程中多生产多消费问题的解决有助于更好的理解多线程的使用。现在通过实例来说明多线程中需要注意的地方。

/**
 * @author zqx 描述资源
 */
public class Resource {
	private String name;
	private int count = 1;
	// 定义flag标记
	private boolean flag;

	// 提供给消费者获取商品的方法

	public synchronized void get() {
		/*用while循环判断是为了解决if判断中出现的重复生产,重复消费的问题,经过分析,发现如果使用if判断,被唤醒的线程没有判断标记
		 * 就开始工作(生产or消费)了。导致了重复的生产和消费的发生。使用while后,所有被唤醒的线程都会首先进行标记的判断。
		 * 
		 * */
		while (!flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName() + "........消费者....." + this.name);
		// 将标记置为false
		flag = false;
		/*
		 * 唤醒等待线程,用notifyAll()方法是为了解决notify()方法时,本方线程在唤醒时,又唤醒了本文线程,而本文线程循环判断标记
		 * 又继续等待,而导致所有的线程都等待了,这样就会导致死锁。使用notifyAll()唤醒所有线程,经过循环判断标记
		 * ,对方线程执行,本文线程等待。
		 */
		this.notifyAll();
	}

	// 提供给生产者生产商品的方法
	public synchronized void set(String name) {
		// 如果falg为true,则说明已经有面包,执行等待,如果flag为false,执行生产。
		while (flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.name = name + "--" + count;
		count++;
		System.out.println(Thread.currentThread().getName() + ".....生产者....." + this.name);
		// 生产完毕,将标记置为true,并唤醒消费者
		flag = true;
		// 唤醒所有等待线程
		this.notifyAll();

	}
}
/**
 * @author zqx 生产者任务
 */
public class Producer implements Runnable {
	private Resource resource;

	Producer(Resource resource) {
		this.resource = resource;
	}

	@Override
	public void run() {
		while (true)
			resource.set("面包");
	}
}
/**
 * @author zqx 消费者任务
 */
public class Custemer implements Runnable {
	private Resource resource;

	Custemer(Resource resource) {
		this.resource = resource;
	}

	@Override
	public void run() {
		while (true)
			resource.get();
	}
}

/**
 * @author zqx
 *	测试类
 */
public class ProducerCustemer {
	public static void main(String[] args) {
		//1、创建资源对象
		Resource resource = new Resource();
		//2、创建两个线程,构造函数的方式初始化两个线程,保证操作同一资源
		Producer producer = new Producer(resource);
		Custemer custemer = new Custemer(resource);
		//3、创建线程,创建多个生产者和消费者
		Thread t0 = new Thread(producer);
		Thread t1 = new Thread(producer);
		Thread t2 = new Thread(custemer);
		Thread t3 = new Thread(custemer);
		//4、开启线程
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}


总结:上述实例已经实现了多生产多消费,但是效率有点低,因为notifyAll也唤醒了本文线程,做了一些不必要的判断,那么有什么更好的方法能够解决上述问题呢?JDK1.5以后出现的Lock接口和Condition接口解决了这一问题

二、多线程Lock接口和Condition接口

  1、作用:

同步函数和同步代码块的锁操作是隐式的,JDK1.5 Lock接口,按照面向对象的思想,将锁单独封装成一个对象,并提供了对锁的显式操作。
Lock接口就是同步的替代。
condition接口替代了Object中的监视器方法,以前监视器方法封装到Condition对象中。

2、常用方法

1、Lock接口中的方法:
lock():获取锁
unlock():释放锁
newCondition():返回绑定到此Lock对象的Condition对象。
2、Condition接口中的方法:
await():使当前线程在接到信号或被中断之前一直处于等待状态。
signal():唤醒一个等待线程
signalAll():唤醒所有等待线程。

3、和同步进行比较

JDK1.4中同步中监视器方法都和锁相绑定,而且一个锁上就只能有一组监视器,如果要实现唤醒对方的一个线程,就要用到锁的嵌套,容易发生死锁。
JDK1.5中监视器方法不再和锁绑定,把监视器方法单独封装成对象,而对象可以和锁绑定,一个锁上可以支持多个相关的condition对象。可以创建两个condion对象,一个用来监视生产,一个用来监视消费,由于是同一个锁,不会用到锁的嵌套,也就不会造成同步中的死锁问题,
对比简图如下:

4、实例

和上述例子唯一不同的就是描述资源的类。其他都相同。用lock和condition来实现多生产多消费问题,解决了效率低的问题。
/**
 * @author zqx 描述资源
 */
public class Resource {
	private String name;
	private int count = 1;
	//1.创建新Lock
	private Lock lock = new ReentrantLock();
	//2.创建和Lock绑定的监视器对象
	//生产者监视器
	private Condition pro_condition = lock.newCondition();
	//消费者监视器
	private Condition cus_condition = lock.newCondition();
	// 定义flag标记
	private boolean flag;

	// 提供给消费者获取商品的方法
	public void get() {
		// 获取锁
		lock.lock();
		try {
			while (!flag) {
				try {
					cus_condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + "........消费者....." + this.name);
			// 将标记置为false
			flag = false;
			//消费完毕,要唤醒一个生产者来生产
			pro_condition.signal();
		} finally {
			// 释放锁
			lock.unlock();
		}
	}

	// 提供给生产者生产商品的方法
	public synchronized void set(String name) {
		// 获取锁
		lock.lock();
		try {
			// 如果falg为true,则说明已经有面包,执行等待,如果flag为false,执行生产。
			while (flag) {
				try {
					pro_condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			this.name = name + "--" + count;
			count++;
			System.out.println(Thread.currentThread().getName() + ".....生产者....." + this.name);
			// 生产完毕,将标记置为true,并唤醒消费者
			flag = true;
			// 生产完毕,应该唤醒一个消费者来消费
			cus_condition.signal();
		} finally {
			// 释放锁
			lock.unlock();
		}
	}
}

、实际开发中相关的实例

以上实例中,一次只能生产一个,但实际开发中可以要连续生产多个,连续消费多个。JDK1.5中有相关实例,现在一起来感受下吧。
以下这段代码相当于描述资源类。有兴趣可以研究下。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值