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喚醒。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.