黑马程序员——生产者消费者问题之线程间通信
2013-05-18 11:57
447 查看
------- android培训、java培训、期待与您交流! ----------
本篇博客将通过代码,分析线程通信的原理。线程通信指的是:一个线程存入数据,另一个线程取出数据。当然,实际中可能出现多个线程存入数据,多个线程读取数据。这里只讨论单个线程写入数据,单个线程读取数据。下面以“生产者消费者”问题为例,分析线程间通信的过程。
为了分析线程通信过程中中会遇到的各种问题,这里循序渐进,先分析线程通信、再分析线程同步、最后才分析线程间通信的生产者消费者问题。
为了解决这些问题,本例将通过wait()和notify()方法对线程进行等待和唤醒。当输入线程输入数据后,输入线程等待,并将输出线程唤醒,让输出线程输出数据。当输出线程输出数据后,将输入线程唤醒,让输入线程输入数据。下面的例子通过标记flag来标记输入和输出。当flag=false时,表示执行输入线程,当flag=true时,表示执行输出线程。
代码如下(为了简便,这里不显示主函数,主函数代码请看1.1节中的内容):
生产者消费者问题分析:生产者是输入线程,消费者是输出线程。当生产者生产商品时(也就是输入线程创建数据),消费者等待。当生产者生产完毕一件商品后,生产者线程等待,消费者线程执行并消费该商品(输出商品)。就只要,生产者和消费者交替执行,只有生产者生产一件视商品,消费者马上消费该商品。为了便于说明,我们该每个商品都编号,每个商品只对应唯一的一个编号,消费者只能消费一次某一个编号的商品,代码如下:
本篇博客将通过代码,分析线程通信的原理。线程通信指的是:一个线程存入数据,另一个线程取出数据。当然,实际中可能出现多个线程存入数据,多个线程读取数据。这里只讨论单个线程写入数据,单个线程读取数据。下面以“生产者消费者”问题为例,分析线程间通信的过程。
为了分析线程通信过程中中会遇到的各种问题,这里循序渐进,先分析线程通信、再分析线程同步、最后才分析线程间通信的生产者消费者问题。
1. 线程通信
线程通信指的是:多个线程操作同一个资源,但是操作动作不同。例如一个线程存储入数据,另一个线程去出数据。1.1线程通信例子——存储与读取不同步问题
下面的例子演示线程通信的过程,有一个类Resource用于产生资源,Input线程用于创建资源,Output出现用于读取Input线程创建的资源。该程序会产生一个问题:我们希望的数据是姓名为"Mike"对应的性别是"man",姓名为"罗林"对应的性别是"女",但是控制台中输出的结果有:Mike——女,罗林——man,这是不安全的。原因是输入数据时,还没有完整输入,输出数据就执行了。也就是说,输入线程还没有执行完run方法时,输出线程就执行run方法读取数据了,所以导致数据不一致的问题。下面一节(1.2)将通过线程同步解决该问题。package com.itheima.entranceExam.blog; //资源类 class Resource { String name; String sex; } //输入线程,该线程用于创建Resource对象的数据 class Input implements Runnable{ private Resource r; public Input(Resource r) { this.r = r; } public void run() { int x = 0; //不停的该Resource对象赋值 while(true) { if(x == 0) { r.name = "Mike"; r.sex = "man"; } else { r.name = "罗琳"; r.sex = "女"; } x = (x+1)%2; } } } //输出线程,该线程用于读取Resource对象的数据 class Output implements Runnable{ private Resource r; public Output(Resource r) { this.r = r; } public void run() { //不停地读取Resource对象的成员变量值 while(true) { System.out.println(r.name+"——"+r.sex); } } } public class ThreadsCommunication { public static void main(String[] args) { Resource r = new Resource(); Input in = new Input(r); Output out = new Output(r); //创建两个线程 Thread t1 = new Thread(in); Thread t2 = new Thread(out); //启动两个线程 t1.start(); t2.start(); } }
1.2 线程同步解决问题
现在我们通过线程同步,解决上面一节(1.1)中出现的问题。我们知道,上面的例子中,当输入线程执行run方法时,正在循环创建数据:Resource.name="Mike"、Resource.sex="man",Resource.name="罗琳"、Resource.sex="女",在此期间输出线程有可能读取这些数据。我们希望只有当输入线程执行完毕这几行创建数据的代码后,输出线程才能读取数据,所以,需要将这几行创建数据的代码放到同步代码块中,输出线程中的输出数据的代码也需要使用同步代码块。这样,我们就可以使用同步代码块来实现两个线程的线程同步,代码如下(为了简便,这里只显示修改部分的代码,Resource类代码和主函数不显示)://输入线程,该线程用于创建Resource对象的数据 class Input implements Runnable{ private Resource r; public Input(Resource r) { this.r = r; } public void run() { int x = 0; //不停的给Resource对象赋值 while(true) { //同步代码块 synchronized (r) { if(x == 0) { r.name = "Mike"; r.sex = "man"; } else { r.name = "罗琳"; r.sex = "女"; } x = (x+1)%2; } } } } //输出线程,该线程用于读取Resource对象的数据 class Output implements Runnable{ private Resource r; public Output(Resource r) { this.r = r; } public void run() { //不停地读取Resource对象的成员变量值 while(true) { //同步代码块 synchronized (r) { System.out.println(r.name+"——"+r.sex); } } } }
1.3 线程间通信-等待唤醒机制
1.2节中的例子,还是存在问题:因为当输入线程获得cup执行权后,就不断地输入数据,而输出线程却无法输出这些及时的数据。当输出线程获得cup执行权后,就不断地输出数据,造成输出的数据相同。下面的图片就说明该问题:为了解决这些问题,本例将通过wait()和notify()方法对线程进行等待和唤醒。当输入线程输入数据后,输入线程等待,并将输出线程唤醒,让输出线程输出数据。当输出线程输出数据后,将输入线程唤醒,让输入线程输入数据。下面的例子通过标记flag来标记输入和输出。当flag=false时,表示执行输入线程,当flag=true时,表示执行输出线程。
代码如下(为了简便,这里不显示主函数,主函数代码请看1.1节中的内容):
package com.itheima.entranceExam.blog; //资源类 class Resource { String name; String sex; //标记:当为false时执行输入线程,当为true时执行输出线程 boolean flag = false; } //输入线程,该线程用于创建Resource对象的数据 class Input implements Runnable{ private Resource r; public Input(Resource r) { this.r = r; } public void run() { int x = 0; //不停的给Resource对象赋值 while(true) { //同步代码块 synchronized (r) { //第一次时,flag为false,所以执行输入线程创建Resource对象的数据 //之后输入线程和输出线程交替执行,输入线程执行完创建数据后,输出线程马上执行输出数据 if(r.flag) try { r.wait();//线程等待 } catch (InterruptedException e) { e.printStackTrace(); } if(x == 0) { r.name = "Mike"; r.sex = "man"; } else { r.name = "罗琳"; r.sex = "女"; } x = (x+1)%2; //改变标记,让输出线程执行 r.flag = true; r.notify();//唤醒线程 } } } } //输出线程,该线程用于读取Resource对象的数据 class Output implements Runnable{ private Resource r; public Output(Resource r) { this.r = r; } public void run() { //不停地读取Resource对象的成员变量值 while(true) { //同步代码块 synchronized (r) { if(!r.flag) try { r.wait();//线程等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(r.name+"——"+r.sex); //改变标记,让输入线程执行 r.flag = false; r.notify();//线程唤醒 } } } }
1.4 优化代码
下面我们优化1.3节中的代码,使其更简单。将1.3节中的同步代码块改写为同步函数,我们不能将run方法改写为同步函数,因为run方法是Runnable类的方法,继承该类的线程类只能重载run方法中的内容,而不能重写。所以,我们可以将输入线程中run方法中的,设置Resource类对象成员变量值的代码,抽取出来放到Resource类的set方法中,并将set方法定义为同步函数。同理,将输出线程中的输出数据的代码抽取出来,放到Resource类的out方法中,并将out方法定义为同步函数。这样,我们就将1.3节中的代码优化了,如下:package com.itheima.entranceExam.blog; //资源类 class Resource { private String name; private String sex; private boolean flag = false; //同步函数:设置Resource类成员变量的值 public synchronized void set (String name, String sex) { if(flag) try { this.wait();//线程等待 } catch (Exception e) {} this.name = name; this.sex = sex; flag = true; this.notify();//唤醒线程 } //同步函数:输出Resource类成员变量的值 public synchronized void out() { if(!flag) { try { this.wait();//线程等待 } catch (Exception e) {} } System.out.println(name+"——"+sex); flag = false; this.notify();//唤醒线程 } } //输入线程 class Input implements Runnable{ private Resource r; public Input(Resource r) { this.r = r; } public void run() { int x = 0; while(true) { if(x == 0) r.set("Mike", "man"); else r.set("罗琳", "女"); x = (x+1)%2; } } } //输出线程 class Output implements Runnable{ private Resource r; public Output(Resource r) { this.r = r; } public void run() { while(true) { r.out(); } } }
2. 线程通信实现生产者消费者问题
通过上面的第1节的学习,我们初步掌握了线程通信的机制,以及如何实现线程同步。现在我们就应用这些知识点来实现“生产者消费者”的问题。生产者消费者问题分析:生产者是输入线程,消费者是输出线程。当生产者生产商品时(也就是输入线程创建数据),消费者等待。当生产者生产完毕一件商品后,生产者线程等待,消费者线程执行并消费该商品(输出商品)。就只要,生产者和消费者交替执行,只有生产者生产一件视商品,消费者马上消费该商品。为了便于说明,我们该每个商品都编号,每个商品只对应唯一的一个编号,消费者只能消费一次某一个编号的商品,代码如下:
package com.itheima.entranceExam.blog; //资源类 class Resource { private String name; //计数器,用来给商品编号 private int count = 1; private boolean flag = false; //同步函数:设置Resource对象的成员变量数据 public synchronized void set(String name) { if(flag) try { this.wait();//线程等待 } catch (Exception e) {} this.name = name+":"+count++; //Thread.currentThread().getName()表示获取当前线程的名称,下同 System.out.println(Thread.currentThread().getName()+" 生产者 "+this.name); flag = true; this.notify();//唤醒线程 } //同步函数:输出Resource类对象的成员变量数据 public synchronized void out() { if(!flag) try { this.wait();//线程等待 } catch (Exception e) {} System.out.println(Thread.currentThread().getName()+" 消费者 "+this.name); flag = false; this.notify();//唤醒线程 } } //生产者 class Producer implements Runnable { private Resource res; public Producer(Resource res) { this.res = res; } public void run() { while(true) { res.set("商品"); } } } //消费者 class Consumer implements Runnable { private Resource res; public Consumer(Resource res) { this.res = res; } public void run() { while(true) { res.out(); } } } public class ProducerConsumer { 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(); } }
相关文章推荐
- 黑马程序员------多线程(No.2)(死锁、线程间通信、生产者消费者问题)
- 黑马程序员——生产者消费者问题之线程间通信
- 黑马程序员自学笔记————多线程 线程间通信之生产者消费者问题;
- 35.黑马程序员-线程间通信(生产者消费者)
- 线程通信,生产者消费者问题(Java)
- 多线程五,线程间通信3,wait、notify,notifyAll方法,生产者和消费者问题(14,毕向东老师)
- 黑马程序员-关于生产者消费者和火车票出售的线程问题
- 多线程(线程间通信-多生产者多消费者问题-JDK1.5解决办法-范例),停止线程,线程中方法的区别,匿名内部类实现多线程,线程总结
- java基础12:线程间通信---生产者消费者问题
- Java线程间通信问题分析(生产者消费者模型)
- 线程间通信、等待唤醒机制、生产者消费者问题(Lock,Condition)、停止线程和守护线程、线程优先级
- Java线程间的相互通信--生产者消费者问题
- 线程间通信--多生产者多消费者问题
- 线程通信(生产者与消费者问题)
- java 线程间通信,多生产者多消费者问题
- 线程间通信-多生产者多消费者问题
- 黑马程序员_Java基础_线程间通信,生产者消费者案例,jdk1.5锁机制,守护线程
- 多线程(线程间通信-多生产者多消费者问题-JDK1.5新特性-Lock
- 黑马程序员--JAVA基础复习之多线程(三)线程间通信 生产者消费者