Java多线程(3)

七、线程间通信
    线程间通信,主要介绍问题的引出和如何解决等内容。

1、问题的引出
  实例:
     把一个数据存储空间划分为两个部分:一部分用于存储人的姓名,另一部分用于存储人的性别。这里包含两个线程:一个线程向数据存储空间添加数据(producer),另一个线程从数据存储空间去除数据(consumer)。这个应用会出现两种意外。
     第一种意外:假设producer刚向数据存储空间添加了一个人的姓名,还没有加入这个人的性别,cpu就切换到consumer线程, consumer 则把这个人的姓名和上一个人的性别联系到了一起。
     第二种意外:producer放入了若干次数据, consumer 才开始取数据,或者是, consumer  取完一个数据后,还没等到producer放入新的数据,又重复取出已去过的数据。
  

2、问题如何解决
   程序中的生产者线程和消费者线程运行的是不同的程序代码,因此编写包含run方法的两个类来完成这两个线程,一个是生产线程Producer,另一个是消费者线程Consumer。
class Producer implements Runnable {
   public void run(){
       //数据空间存放数据
   }
}
class Consumer implements Runnable{
   public void run(){
      //从数据空间读取数据
   }

还需要定义一个数据结构来存储数据
class P{
   String name;
   String sex;

范例1:
class P{
   String name = "LiSi";
   String sex = "Girl";
}
class Producer implements Runnable {
   P q = null;
   public Producer(P q){
     this.q = q;
   }
   public void run(){
     int i = 0;
     while(true){
        if(i==0){
          q.name = "ZhangSan";
          q.sex = "Boy";
        }else{
          q.name = "Lisi";
          q.sex = "Girl";
        }
        i = (i+1)%2;
     }
   }
}
class Consumer implements Runnable{
   P q = null;
   public Consumer (P q){
     this.q = q;
   }
   public void run(){
     while(true){
        System.out.println(q.name+"----->"+q.sex);
     }
   }
}
public class Test {
   public static void main(String args[]){
       P q = new P();
       Thread pro = new Thread(new Producer(q));
       Thread con = new Thread(new Consumer(q));
       pro.start();
       con.start();
   }
}

运行结果片段:
......
Lisi----->Girl
ZhangSan----->Boy
ZhangSan----->Boy
Lisi----->Girl
Lisi----->Boy
ZhangSan----->Girl
ZhangSan----->Boy
ZhangSan----->Girl
ZhangSan----->Boy
Lisi----->Boy
Lisi----->Boy
ZhangSan----->Boy
ZhangSan----->Girl
......

   从运行结果来看,打印出现了混乱的情况,这是什么原因?在程序中,Producer类和Consumer类都操纵了同一个P类,这就是有可能出现Producer类还未操纵玩P类,Consumer类就已经将P类中的内容取走了,这就是资源不同步的原因。为此,可在P类中增加两个同步方法:set()和get()。

范例2:
class P{
   String name = "LiSi";
   String sex = "Girl";
   public synchronized void set(String name,String sex){
        this.name = name;
        this.sex = sex;
   }
   public synchronized void get(){
        System.out.println(this.name+"----->"+this.sex);
   }
}
class Producer implements Runnable {
   P q = null;
   public Producer(P q){
     this.q = q;
   }
 
   public void run(){
     int i = 0;
     while(true){
        if(i==0){
          q.set("ZhangSan","BOy");
        }else{
          q.set("LiSi","Girl");
        }
        i = (i+1)%2;
     }
   }
}
class Consumer implements Runnable{
   P q = null;
   public Consumer (P q){
     this.q = q;
   }
   public void run(){
     while(true){
        q.get();
     }
   }
}
public class Test {
   public static void main(String args[]){
       P q = new P();
       Thread pro = new Thread(new Producer(q));
       Thread con = new Thread(new Consumer(q));
       pro.start();
       con.start();
   }
}
运行结果:
........
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
LiSi----->Girl
.......
    这个输出结果顺序上没有任何问题,但又有新的问题产生。Consumer线程对Producer线程放入的一次数据连续的读取了多次,这并不符合实际的要求。实际要求的结果是,Producer方一次数据,Consumer就取一次;反之,Producher也必须等到Consumer取完后才能放入新的数据,而这一问题的解决就需要使用下面讲到的线程间的通信。
    Java是通过Object类的wait、notify、notifyAll这几个方法来实现线程间的通信的,又因为所有的类都是从Object继承的,所以任何类都可以直接使用这些方法。
wait:告诉当前线程放弃监视器并进入睡眠状态,直到其他线程进入同一监视器并调用notify为止;
nofity:唤醒同一对象监视器中调用wait的第一个线程。这类似于排队买票,一个人买完之后,后面的人才可以继续买;
notifyAll: 唤醒同一对象监视器中调用wait的所有线程,具有最高优先级的线程首先被唤醒并执行。
    我们现在将P类修改如下:
class P{
   String name = "LiSi";
   String sex = "Girl";
   boolean bFull = false;
   public synchronized void set(String name,String sex){
        if(bFull){
           try{
              wait();
           }catch(InterruptedException e){}
        }
        this.name = name;
        try{
           Thread.sleep(10);
        }catch(Exception e){}
        this.sex = sex;
        bFull = true;
        notify();
   }
   public synchronized void get(){
        if(!bFull){
            try{
               wait();
            }catch(InterruptedException e){}
        }
        System.out.println(this.name+"----->"+this.sex);
        bFull = false;
        notify();
   }
}
   当Consumer线程取走数据后, bFull值为false,当Producer线程放入数据后, bFull值为true。只有 bFull为true时,Consumer线程才能取走数据,否则就必须等待Producer线程放入新的数据后的通知;反之,只有 bFull为false,Producer线程才能放入新的数据,否则就必须等待Consumer线程取走数据后的通知。
运行结果如下:
ZhangSan----->BOy
LiSi----->Girl
ZhangSan----->BOy
LiSi----->Girl
ZhangSan----->BOy
LiSi----->Girl
ZhangSan----->BOy
LiSi----->Girl
ZhangSan----->BOy
LiSi----->Girl
ZhangSan----->BOy
LiSi----->Girl
ZhangSan----->BOy
LiSi----->Girl
ZhangSan----->BOy
LiSi----->Girl
ZhangSan----->BOy
LiSi----->Girl
ZhangSan----->BOy
LiSi----->Girl
    wait、notify、notifyAll这3个方法只能在synchronized方法中调用,即无论线程调用一个对象的wait还是notify方法,该线程必须先得到该对象的锁标记。这样,notify就只能唤醒同一对象监视器中调用wait的线程。而使用多个对象监视器,就可以分别有多个wait、notify的情况,同组里的wait只能被同组的notify唤醒。

你可能感兴趣的:(java,多线程,线程)