您的位置:首页 > 职场人生

黑马程序员——生产者消费者问题之线程间通信

2013-05-18 11:57 447 查看
------- android培训java培训、期待与您交流! ----------

本篇博客将通过代码,分析线程通信的原理。线程通信指的是:一个线程存入数据,另一个线程取出数据。当然,实际中可能出现多个线程存入数据,多个线程读取数据。这里只讨论单个线程写入数据,单个线程读取数据。下面以“生产者消费者”问题为例,分析线程间通信的过程。

为了分析线程通信过程中中会遇到的各种问题,这里循序渐进,先分析线程通信、再分析线程同步、最后才分析线程间通信的生产者消费者问题。

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();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐