同属于一个进程的多个线程,是共享地址空间的,它们可以一起协作来完成指定的任务。因此,线程之间必须相互通信,才能完成协作。
一、引入问题
下面通过一个应用案例来讲解线程间的通信。把一个数据储存空间划分为两个部分:一部分用于储存用户的姓名,另一部分用于储存用户的性别。
这个案例包含两个线程:一个线程向数据存储空间添加数据(生产者),另一个线程从数据存储空间中取出数据(消费者)。这个程序有两种意外需要考虑:
第一种意外,假设生产者线程刚向数据储存空间中添加了一个人的姓名,还没有加入这个人的性别,CPU就切换到了消费者线程,消费者线程则把这个人的姓名和上一个人的性别联系到一起。这个过程可用下图表示:
第二种意外,生产者放入了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没等到生产者放入新的数据,又重新取出已取过的数据。
在操作系统里,上面的案例属于经典的同步问题——生产者消费者问题,下面我们通过线程间的通信来解决上面提到的意外:
二、解决问题
下面先来构思这个程序,程序中的生产者线程和消费者线程运行的是不同的程序代码,因此这里需要编写两个包含有run方法的类来完成这两个线程,一个是生产者类Producer,另一个是消费者类Consumer。
01 class Producer implements Runnable
02 {
03 public void run()
04 {
05 while(true)
06 {
07 //编写往数据存储空间中放入数据的代码
08 }
09 }
10 }
下面是消费者线程的代码:
01 class Consumer implements Runnable
02 {
03 public void run()
04 {
05 while(true)
06 {
07 //编写从数据存储空间中读取数据的代码
08 }
09 }
10 }
当程序写到这里,还需要定义一个新的数据结构Person,用来作为数据储存空间。 在这个数据结构中,类Person只有数据,而没有对数据的操作,非常类似于C语言的结构体。
01 class Person
02 {
03 String name;
04 String sex;
05 }
Producer和Consumer线程中的run()方法都需要操作类Person的同一对象实例。
接下来,对Producer和Consumer这两个类做如下修改,顺便写出程序的主调用类ThreadCommunation:
package com.xy.thread;
class Person {
String name = "小四";
String sex = "女";
}
class Producer implements Runnable {
Person p = null;
public Producer(Person p) {
this.p = p;
}
public void run() {
for(int i = 0; i < 10; i++) {
if(i%2 == 0) {
p.name = "小三";