多线程【生产者消费者模型】
生产者消费者问题
在生活中经常会遇到两方都在处理某一资源,而处理的方式不同。比如:水池中注水和排水,煤场中往进运煤和往出拉煤。这些操作处理的资源都相同,只是他们操作的方式有所不同。这类操作就多线程中另外一种高级应用,即多生产和多消费。
生产者消费者代码
多线程中最为常见的应用案例:生产者消费者问题。
举例:生产者在生产
商品,而消费者在消费
生产的商品。生产者把生产的商品放进容器中,而消费者从容器中取出商品进行消费。可是在整个过程中,如果容器装满了,那么生产者应该停止生产,如果容器中没有商品了,消费应该停止消费。这就是一个典型的多生产,多消费的案例。
在学习过程中,为了代码简单明了,大家很容易看懂,就把上述的多生产和多消费进行简化,要求生产者生产一个商品,消费者消费一个商品,然后生产者继续生产,消费者进行消费,以此类推下去。
分析案例:
生产和消费同时执行,需要多线程
。但是执行的任务却不相同,处理的资源确实相同的:线程间的通信
。
思路:
- 描述处理的资源。
- 描述生产者,具备着生产的任务。
- 描述消费者,具备着消费的任务。
public class ThreadDemo1 {
public static void main(String[] args) {
// 创建资源对象
Resource r = new Resource();
// 创建生产者对象
Producer pro = new Producer(r);
// 创建消费者对象
Consumer con = new Consumer(r);
// 创建线程对象
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
// 开启线程
t1.start();
t2.start();
System.out.println("Hello World!");
}
}
// 描述资源。属性:商品名称和编号,行为:对商品名称赋值,获取商品。
class Resource {
private String name;// 商品名称
private int count = 1;// 编号
// 对外提供设置商品的方法
public void set(String name) {
// 给成员变量赋值并加上编号。
this.name = name + count;
// 编号自增。
count++;
// 打印生产了哪个商品。
System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
}
// 对外消费商品的方法
public void get() {
System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
}
}
//描述生产者
class Producer implements Runnable {
// 资源属性
private Resource r;
// 生产者一创建就应该明确资源
Producer(Resource r) {
this.r = r;
}
// 生产者生产商品的任务
public void run() {
// 生产者无限制的生产
while(true) {
r.set("面包");
}
}
}
//描述消费者
class Consumer implements Runnable {
// 资源属性
private Resource r;
//生产者以创建就应该明确资源
Consumer(Resource r) {
this.r = r;
}
//生产者生产商品的任务
public void run() {
while(true) {
r.get();
}
}
}
生产者消费者问题的发生
上述代码进行运行时发现有严重的问题。
问题描述:
数据错误:已经被生产很早期的商品,才被消费到。
出现线程安全问题,加入了同步解决。使用同步函数。
// 描述资源。属性:商品名称和编号,行为:对商品名称赋值,获取商品。
class Resource {
private String name;// 商品名称
private int count = 1;// 编号
// 对外提供设置商品的方法
public synchronized void set(String name) {
// 给成员变量赋值并加上编号。
this.name = name + count;
// 编号自增。
count++;
// 打印生产了哪个商品。
System.out.println(Thread.currentThread().getName() + ".....生产者...." + this.name);
}
// 对外消费商品的方法
public synchronized void get() {
System.out.println(Thread.currentThread().getName() + ".....消费者...." + this.name);
}
}
加入同步后又有新的问题产生了。
问题描述:
流程错误:发现了连续生产却没有消费,同时对同一个商品进行多次消费。
当容器中没有面包时,就生产,如果有了面包,就不要生产。
当容器中已有面包时,就消费,如果没有面包,就不要消费。
等待唤醒机制
生产者生产了商品后应该告诉消费者来消费。这时的生产者应该处于等待状态。消费者消费了商品后,应该告诉生产者,这时消费者处于等待状态。
等待:wait();
通知:notify();
public class ThreadDemo2 {
public static void main(String[] args) {
// 创建资源对象
Resource r = new Resource();
// 创建生产者对象
Producer pro = new Producer(r);
// 创建消费者对象
Consumer con = new Consumer(r);
// 创建线程对象
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
// 开启线程
t1.start();
t2.start();
System.out.println("Hello World!");
}
}
//描述资源。属性:商品名称和编号,行为:对商品名称赋值,获取商品。
class Resource {
private String name;// 商品名称
private int count = 1;// 编号
private boolean flag = false;
// 对外提供设置商品的方法
public synchronized void set(String name) {
if (flag) {
try {
wait();
} catch (InterruptedException e) {
System.out.println(e);
}
}
// 给成员变量赋值并加上编号。
this.name = name + count;
// 编号自增。
count++;
// 打印生产了哪个商品。
System.out.println(Thread.currentThread().getName() + ".....生产者...." + this.name);
// 将标记改为true。
flag = true;
// 唤醒消费者。
this.notify();
}
// 对外提供获取商品
public synchronized void get() {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
System.out.println(e);
}
}
System.out.println(Thread.currentThread().getName() + ".....消费者...." + this.name);
// 将标记改为false。
flag = false;
// 唤醒生产者。
this.notify();
}
}
// 描述生产者
class Producer implements Runnable {
private Resource r;
// 生产者以创建就应该明确资源
Producer(Resource r) {
this.r = r;
}
// 生产者生产商品的任务
public void run() {
// 生产者无限制的生产
while (true) {
r.set("面包");
}
}
}
// 描述消费者
class Consumer implements Runnable {
private Resource r;
// 生产者以创建就应该明确资源
Consumer(Resource r) {
this.r = r;
}
// 生产者生产商品的任务
public void run() {
while (true) {
r.get();
}
}
}
等待/唤醒机制:
wait()
: 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。
notify()
:会唤醒线程池中任意一个等待的线程。
notifyAll()
:会唤醒线程池中所有的等待线程。
记住:这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁。同一个锁上的notify,只能唤醒该锁上的被wait的线程。
为什么这些方法定义在Object类中:
因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然时Object类中的方法。
多生产多消费问题以及解决方案
上述程序只是一个生产和一个消费者,其实就是所谓的单生产和单消费,可是我们都知道生活中经常会有多个生产者和消费者,把代码改为多个生产者或多个消费者。
class ThreadDemo3
{
public static void main(String[] args)
{
//创建资源对象
Resource r = new Resource();
//创建生产者对象
Producer pro = new Producer(r);
//创建消费者对象
Consumer con = new Consumer(r);
//创建线程对象
Thread t1 = new Thread(pro);// 线程1生产
Thread t2 = new Thread(pro);// 线程2生产
Thread t3 = new Thread(con);// 线程3消费
Thread t4 = new Thread(con);// 线程4消费
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
把生产者和消费者改为多个时,又有新的问题发生了。
问题描述:
流程错误:生产了商品没有被消费,同一个商品被消费多次。
问题原因: 只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while循环标记。记住:多生产多消费,必须是while循环条件。
解决:只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while循环标记。记住:多生产多消费,必须时while循环条件。
//描述资源。属性:商品名称和编号, 行为:对商品名称赋值,获取商品。
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
//对外提供设置商品的方法
public synchronized void set(String name)
{
while(flag)
{
try{wait();}catch(InterruptedException e){}
}
//给成员变量赋值并加上编号。
this.name = name + count;
//编号自增。
count++;
//打印生产了哪个商品。
System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
//将标记改为true。
flag = true;
//唤醒消费者。
this.notify();
}
public synchronized void get()
{
while(!flag)
{
try{wait();}catch(InterruptedException e){}
}
System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
//将标记改为false。
flag = false;
//唤醒生产者。
this.notify();
}
}
当把if改为while之后又出现问题了。
问题描述:
流程错误:发现while判断后,死锁了。
原因:本方唤醒了本方生产方,唤醒了线程池中生产方的线程。
解决:希望本方要唤醒对方,没有对应的方法,所以只能唤醒所有。
整合所有的完整代码:
public class ThreadDemo4 {
public static void main(String[] args) {
//创建资源对象
Resource r = new Resource();
//创建生产者对象
Producer pro = new Producer(r);
//创建消费者对象
Consumer con = new Consumer(r);
//创建线程对象
Thread t1 = new Thread(pro);// 线程1生产
Thread t2 = new Thread(pro);// 线程2生产
Thread t3 = new Thread(con);// 线程3消费
Thread t4 = new Thread(con);// 线程4消费
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
// 描述生产者
class Producer implements Runnable {
private Resource r;
// 生产者以创建就应该明确资源
Producer(Resource r) {
this.r = r;
}
// 生产者生产商品的任务
public void run() {
// 生产者无限制的生产
while (true) {
r.set("面包");
}
}
}
// 描述消费者
class Consumer implements Runnable {
private Resource r;
// 生产者以创建就应该明确资源
Consumer(Resource r) {
this.r = r;
}
// 生产者生产商品的任务
public void run() {
while (true) {
r.get();
}
}
}
//描述资源。属性:商品名称和编号, 行为:对商品名称赋值,获取商品。
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
//对外提供设置商品的方法
public synchronized void set(String name) {
while(flag) {
try{wait();}catch(InterruptedException e){}
}
//给成员变量赋值并加上编号。
this.name = name + count;
//编号自增。
count++;
//打印生产了哪个商品。
System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
//将标记改为true。
flag = true;
//唤醒消费者。
this.notifyAll();
}
public synchronized void get() {
while(!flag) {
try{wait();}catch(InterruptedException e){}
}
System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
//将标记改为false。
flag = false;
//唤醒生产者。
this.notifyAll();
}
}