一、生产者、消费者模型

1.1 概述

  • 生产者和消费者是线程间通信的一种模型,这个问题是线程模型中的一个经典问题:

生产者和消费者在同一时间段内共用同一段存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。

这里我们可以举一个简单的例子,上菜!

java 生产者与消费者 鸡 生产者与消费者模型 java_并发编程

厨师利用原材料做好一份美味的菜肴————生产者 制造数据

厨师将做好的菜肴放入待取餐区餐柜,敲响取餐铃,通知客人取餐————生产者 将数据放入缓冲区

客人从待取餐区餐柜取走自己的餐食————消费者 把数据从缓冲区取出

客人进食自己的餐食————消费者 加工/处理数据

1.2 相关方法

java 生产者与消费者 鸡 生产者与消费者模型 java_内部类_02

  • 生产者消费者模型中

消费者暂停等待数据

生产者产生数据后,敲铃,发出通知

  • Object 的方法

如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁

wait(),将当前线程置于“就绪队列”,并在wait()所在的代码处停止,等待唤醒通知。

wait(long timeout),将当前线程置于“就绪队列”,并在wait()所在的代码处停止,等待唤醒通知(其他线程调用notify(), notifyAll(),或超过指定的时间量)。

wait(long timeout, int nanos),将当前线程置于“就绪队列”,并在wait()所在的代码处停止,等待唤醒通知(其他线程调用notify(), notifyAll(),或其他某个线程中断当前线程,或超过指定的时间量)。

 

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

notify(),只唤醒一个等待(对象的)线程,该线程可竞争对象锁。如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。

notifyAll(),唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。

 

例如,

调用 stack.wait() ,当前线程,在 stack 对象上等待

调用 stack.notifyAll() ,在 stack 上发出通知,通知 / 唤醒在stack对象上等待的线程

 

  • 上述方法在使用时,必须在 synchronized 同步代码内调用
  • 上述方法在使用时,必须在加锁的对象上等待或通知
synchronized(a) { 
    a.wait() 
    a.nitifyAll() 
}

  • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它会继续留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
  • 为了保证安全,wait()外面总应该是一个循环判断

在多线程中,循环判断(或要测试某个条件的变化)使用if 还是while?

要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行。

1.3 练习:生产者、消费者模型的简单实现


package 生产者消费者模型;

/*
 *	生产者消费者模型-数据仓库Stack(即我们上面提到的数据缓冲区)
 */
public class Stack {

	private char[] c = new char[5];
	private int index;

	// 入栈操作
	public void push(char tmp) {
		if (isFull()) {
			return;
		}
		c[index] = tmp;
		index++;
	}

	// 弹栈操作
	public char pop() {
		if (isEmpty()) {
			return ' ';// 用“空格”表示没有数据
		}
		index--;
		char tmp = c[index];
		return tmp;
	}

	// 判断Stack数据是已满
	public boolean isFull() {
		return index == c.length;
	}

	// 判断Stack数据是否为空
	public boolean isEmpty() {
		return index == 0;
	}

	// 返回栈对象
	public char[] getStack() {
		return this.c;
	}

	// 返回当前栈顶的数据下标
	public int getIndex() {
		return this.index;
	}
}


package 生产者消费者模型;

import java.util.Random;

/*
 *	生产者消费者模型-生产者类
 */
public class Producer extends Thread{
	private Stack stack;
	
	// 构造传入数据库,与消费者共同维护一个数据库
	public Producer(Stack stack) {
		this.stack = stack;
	}

	@Override
	public void run() {
		while(true) {
			// ['a','a+26')='a'+[0,26),随机产生英文小写字符
			char c =(char)('a'+new Random().nextInt(26));
			// synchronized同步代码块,对象锁
			synchronized (this.stack) {
				// 添加“等待”,解决数据读取冲突
				/*	循环判断苏醒条件,避免其他进程误操作唤醒后读取错误或空数据	
				 */
				while(stack.isFull()) {
					// Stack没有数据的话,
					try {
						stack.wait();
					} catch (InterruptedException e) {
						// 暂不处理异常
					}
				}
				
				stack.push(c);// 生产者数据入栈
				// 通知“消费者”已放入一个数据,有一个数据可取
				stack.notifyAll();
				System.out.println("入栈<<"+c);
			}
		}
	}
}
package 生产者消费者模型;
/*
 *	生产者消费者模型-消费者类
 */
public class Consumer extends Thread{
	private Stack stack;

    // 构造传入数据库,与生产者共同维护一个数据库
	public Consumer(Stack stack) {
		this.stack = stack;
	}

	    @Override
		public void run() {
		while(true) {
			// synchronized同步代码块,对象锁
			synchronized (this.stack) {
				// 添加“等待”,解决数据读取冲突
				/*	循环判断苏醒条件,避免其他进程误操作唤醒后读取错误或空数据	
				 */
				while(stack.isEmpty()) {
					// Stack没有数据的话,
					try {
						stack.wait(); // 等待
					} catch (InterruptedException e) {
						// 暂不处理异常
					}
				}
				
				char c = stack.pop();
				// 通知“生产者”已取出一个数据,有一个位置可放
				stack.notifyAll();
				System.out.println("弹栈>>"+c);// 栈空时,弹出空格字符	
			}
		}
	}
}
package 生产者消费者模型;

public class Test1 {
	public static void main(String[] args) {
		Stack stack = new Stack();
		Producer p = new Producer(stack);// 生产者线程
		Consumer c = new Consumer(stack);// 消费者线程
		
		p.start();
		c.start();
		
		// main线程,死循环发通知唤醒等待的线程进行相应的条件判断,不动数据
		// 通过进程在“notify”后,重复判断是否有数据即可
		while(true) {
			synchronized (stack) {
				stack.notifyAll();
			}
		}
	}
}

1.4 线程监视器模型

  • 同步监视器
  • 遇到 synchronized 关键字,在加锁的对象上会关联一个同步监视器