7.3 多線程之間的通訊
7.3.1 生產(chǎn)者和消費(fèi)者
多線程的一個(gè)重要特點(diǎn)是它們之間可以互相通訊。你可以設(shè)計(jì)線程使用公用對(duì)象,每個(gè)線程都可以獨(dú)立操作公用對(duì)象。典型的線程間通訊建立在生產(chǎn)者和消費(fèi)者模型上:一個(gè)線程產(chǎn)生輸出;另一個(gè)線程使用輸入。
buffer讓我們創(chuàng)建一個(gè)簡(jiǎn)單的"Alphabet Soup"生產(chǎn)者和相應(yīng)的消費(fèi)者.
7.3.2 生 產(chǎn) 者
生產(chǎn)者將從thread類里派生:
class Producer extends Thread {
private Soup soup;
private String alphabet = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public Producer(Soup s) {
//Keep our own copy of the shared object
this.soup = s;
}
public void run() {
char c; //Throw 10 letters into the soup
for (int i=0;i<10;i++) {
c = alphabet.charAt((int)(Math.random() *26));
soup.add(c); //print a record of osr addition
System.out.println("Added"+c + "to the soup.");
//wait a bit before we add the next letter
try {
sleep((int)(Math.random() *1000));
}
catch (InterruptedException e) {
//
}
}
}
}
注意我們創(chuàng)建了Soup類的一個(gè)實(shí)例。生產(chǎn)者用soup.add()函數(shù)來(lái)建立字符池。
7.3.3 消費(fèi)者
讓我們看看消費(fèi)者的程序:
public class Consumer extends Thread {
private Soup soup;
public Consumer (Soup s) {
//keep our own copy of the shared object
this.soup = s;
}
public void run() {
char c;
//Eat 10 letters from the alphabet soup
for (int I=0 ;i<10;i++) {
//grab one letter
this.c = soup.eat();
//Print out the letter that we retrieved
System.out.println("Ate a letter: " +c); //
try {
sleep((int)(Math.raddom()*2000));
}
catch (InterruptedException e) {
}
` }
}
}
同理,象生產(chǎn)者一樣,我們用soup.eat()來(lái)處理信息。那么,Soup類到底干什么呢?
7.3.4 監(jiān) 視
Soup類執(zhí)行監(jiān)視兩個(gè)線程之間傳輸信息的功能。監(jiān)視是多線程中不可缺少的一部分,因?yàn)樗3至送ㄓ嵉牧鲿?。讓我們看看Soup.java文件:
public class Soup {
private char buffer[] = new char[6];
private int next = 0; //Flags to keep track of our buffer status
private boolean isFull = false;
private boolean isEmpty = true;
public syschronized char eat() {
//We can't eat if there isn't anything in the buffer
while (isEmpty == true) {
try {
wait() ;//we'll exit this when isEmpty turns false
catch (InterruptedException e) {
}
}
//decrement the count,since we're going to eat one letter next--;
//Did we eat the last letter?
if (next== 0) {
isEmpty = true;
}
//We know the buffer can't be full,because we just ate
isFull = false;
notify(); //return the letter to the thread that is eating
return (buffer[next]);
}
//method to add letters to the buffer
public synchronized void add(char c) {
//Wait around until there's room to add another letter
while (isFull == true ) {
try{
wait();//This will exit when isFull turns false
}
catch (InterruptedException e) {
}
} // add the letter to the next available spot
buffer[next]=c; // Change the next available spot
next++; // Are we full;
if (next ==6) {
isFull =true;
}
isEmpty =false;
notify();
}
}
soup類包含兩個(gè)重要特征:數(shù)據(jù)成員buffer[]是私有的,功能成員add()和eat()是公有的。
數(shù)據(jù)私有避免了生產(chǎn)者和消費(fèi)者直接獲得數(shù)據(jù)。直接訪問(wèn)數(shù)據(jù)可能造成錯(cuò)誤。例如,如果消費(fèi)者企圖從空緩沖區(qū)里取出數(shù)據(jù),你將得到不必要的異常,否則,你只能鎖住進(jìn)程。同步訪問(wèn)方法避免了破壞一個(gè)共享對(duì)象。當(dāng)生產(chǎn)者向soup里加入一個(gè)字母時(shí),消費(fèi)者不能吃字 符,諸如此類。這種同步是維持共享對(duì)象完整性的重要方面。notify()函數(shù)將喚醒每一個(gè)等待線程。等待線程將繼續(xù)它的訪問(wèn)。
7.3.5 聯(lián)系起來(lái)
現(xiàn)在我們有一個(gè)生產(chǎn)者,一個(gè)消費(fèi)者和一個(gè)共享對(duì)象,怎樣實(shí)現(xiàn)它們的交互呢? 我們只需要一個(gè)簡(jiǎn)單的控制程序來(lái)啟動(dòng)所有的線程并確信每一個(gè)線程都是訪問(wèn)的同一個(gè)共享對(duì)象。下面是控制程序的代碼:
SoupTest.java:
class SoupTest {
public static void main(String args[]) {
Soup s = new Soup();
Producer p1 = new Producer(s);
Consumer c1 = new Consumer(s);
p1.start();
c1.start();
}
}
7.3.6 監(jiān)視生產(chǎn)者
生產(chǎn)者/消費(fèi)者模型程序經(jīng)常用來(lái)實(shí)現(xiàn)遠(yuǎn)程監(jiān)視功能,它讓消費(fèi)者看到生產(chǎn)者同用戶的交互 或同系統(tǒng)其它部分的交互。 例如,在網(wǎng)絡(luò)中,一組生產(chǎn)者線程可以在很多工作站上運(yùn)行。 生 產(chǎn)者可以打印文檔,文檔打印后,一個(gè)標(biāo)志將保存下來(lái)。一個(gè)(或多個(gè)消費(fèi)者將保存標(biāo)志并 在晚上報(bào)告白天打印活動(dòng)的情況。另外,還有例子在一個(gè)工作站是分出幾個(gè)獨(dú)立的窗口。 一 個(gè)窗口用作用戶輸入(生產(chǎn)者的另一個(gè)窗口作出對(duì)輸入的反應(yīng)消費(fèi)者的。

