线程通信之生产者消费者详解

先前我们讲解了多线程互斥访问同一资源问题,接下来我们讲解线程间通信问题之经典:生产者与消费者

线程通信:不同的线程在执行不同的任务,如果这些任务的执行之间存在先后顺序,那么线程之间必须要能够通信,协调的完成工作。


(一)经典案例:

(1)生产者和消费者等多个线程在访问共同资源时, 必须要互斥(即要访问的临界资源一样),只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问

(2)问题:为什么生产者不直接将生产好的产品直接交给消费者消费呢?

在这里体现了面向对象的设计理念:低耦合;

a)高耦合:生产者直接将生产好的产品交给消费者,那么生产者中药封装一个消费者对象的引用;消费者要消费生产者的产品,那么消费者就要封装一个生产者对象的引用,例如:主板和集成显卡;

b)低耦合:使用一个中间对象,屏蔽掉生产者和消费者之间直接数据交互的过程,例如:主板和独立显卡。


(二)线程通信之wait()和notify()、notifyAll()方法详解:

(1)先了解两个概念:

a)线程等待池(没有cpu资源,没有同步资源的监听器(同步锁),连获取同步锁的权利都没有,此线程就没有执行的可能);

b)锁池(拥有cpu资源,但是没有同步锁,但有获取同步锁的权利,多线程之间一旦获取到锁,就会立即执行);

(2)java.lang.Object类中提供了几个方法用于操作多线程之间的通信问题:

wait():执行该方法的线程对象释放同步锁,JVM把该线程存放在共享资源的等待池中,等待其他线程唤醒;

notify():执行该方法的线程,将唤醒共享资源等待池中任意、随机的一个线程。将其线程转移到锁池中;

notifyAll():执行该方法的线程将唤醒共享资源等待池中所有的线程,将其线程转移到锁池中。

注解:共享资源即为同步锁,也称为同步监听对象。


注意:

1)上述的三个方法只能被同步监听对象调用(即就是只能放在synchronized关键字修饰的同步代码块/同步方法中共享资源对象(this)来调用),否则报错illegalMonitorStateException异常;

2)多个线程只有共享同一资源,多线程之间才有互斥现象,我们将其共享资源称为:同步锁/同步监听对象/同步监听器

3)因为只有同步监听对象才可以调用wait(),notify()以及notifyAll()方法,所以这些方法存在于Object类中,而不在Thread类中(只用让同步监听对象调用,才可以知道要释放的是哪个锁,唤醒的是哪个需要当前同步锁的线程对象)。


(三)生产者与消费代码:

生产者代码:

/**
 * 
 * @author super 生产者用于生产数据
 */
public class Producer implements Runnable {

	// 共享的资源对象(同一个资源对象)
	private ShareResource resource = null;

	public Producer(ShareResource resource) {
		super();
		this.resource = resource;
	}

	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			//生产操作
			if (i % 2 == 0) {
				resource.push("Jack", "男");
			} else {
				resource.push("Tom", "女");
			}
		}
	}
}

消费者代码:

/**
 * 
 * @author super
 * 消费者
 */
public class Consumer implements Runnable{
	
	// 共享的资源对象(同一个资源对象)
	private ShareResource resource = null;
	
	public Consumer(ShareResource resource) {
		super();
		this.resource = resource;
	}

	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			//消费操作
			resource.popup();
		}
	}
}


共享资源(同步锁代码):

/**
 * 
 * 共享资源对象(姓名-性别)
 */
public class ShareResource {

	private String name;
	private String gender;
	//表示共享资源为空的状态(true为空就生产,fasle不为空就消费)
	private boolean isEmpty = true;

	/*
	 * 用于生产者向共享资源对象中存储数据
	 */
	public synchronized void push(String name,String gender){
		try {
			while(!isEmpty){ //不为空,等待消费者来消费
				//释放同步锁,进入resource资源的等待池中,只能被其他线程所唤醒
				this.wait();	
			}
			//------生产开始------
			this.name = name;
			Thread.sleep(10);
			this.gender = gender;
			//------生产结束------
			isEmpty = false;	//设置共享资源中的数据不为空
			//生产者生产完毕,就必须唤醒一个消费者,否则出现阻塞状态,全部都进入等待状态
			this.notify();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/*
	 * 用于消费者从共享资源对象中取出数据
	 */
	public synchronized void popup(){
		try {
			while(isEmpty){	//isEmpty = true时,共享资源为空,等待生产者进行生产
				//释放同步锁,进入resource资源的等待池中,只能被其他线程所唤醒
				this.wait();
			}
			//------消费开始------
			Thread.sleep(10);
			System.out.println(this.name + "-" + this.gender);
			//------消费结束------
			isEmpty = true; //设置共享资源中的数据为空
			//消费者消费完毕,就必须唤醒一个生产者,否则出现阻塞状态,全部都进入等待状态
			this.notify();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


测试代码:

/**
 * 
 * @author lenovo
 * 测试代码
 */
public class Test {

	public static void main(String[] args) {
		//创建生产者和消费者共同的资源对象
		ShareResource resource = new ShareResource();
		
		//启动生产者线程
		new Thread(new Producer(resource)).start();
		//启动消费者线程
		new Thread(new Consumer(resource)).start();
	}
}


重中之重:如果你使用了多个生产者,或者多个消费者线程,一定要在共享资源中每一处使用了notify()方法,唤醒在其同步监听对象的全部线程(即使用notifyAll()方法,而不是notify()方法),一定要切记,否者就会出现多个线程全部等待,无一唤醒,程序就死了!




猜你喜欢

转载自blog.csdn.net/super_yc/article/details/73469100