這一章的許多謎題都涉及到了多線程,而這個謎題涉及到了多進程。如果你用一行命令行帶上參數(shù)slave去運行這個程序,它會打印什么呢?如果你使用的命令行不帶任何參數(shù),它又會打印什么呢?
public class BeerBlast{
static final String COMMAND = "java BeerBlast slave";
public static void main(String[] args) throws Exception{
if(args.length == 1 && args[0].equals("slave")) {
for(int i = 99; i > 0; i--){
System.out.println( i +
" bottles of beer on the wall" );
System.out.println(i + " bottles of beer");
System.out.println(
"You take on down, pass it around,");
System.out.println( (i-1) +
" bottles of beer on the wall");
System.out.println();
}
}else{
// Master
Process process = Runtime.getRuntime().exec(COMMAND);
int exitValue = process.waitFor();
System.out.println("exit value = " + exitValue);
}
}
}
如果你使用參數(shù)slave來運行該程序,它就會打印出那首激動人心的名為”99 Bottles of Beer on the Wall”的童謠的歌詞,這沒有什么神秘的。如果你不使用該參數(shù)來運行這個程序,它會啟動一個slave進程來打印這首歌謠,但是你看不到slave進程的輸出。主進程會等待slave進程結(jié)束,然后打印出slave進程的退出值(exit value)。根據(jù)慣例,0值表示正常結(jié)束,所以0就是你可能期望該程序打印的東西。如果你運行了程序,你可能會發(fā)現(xiàn)該程序只會懸掛在那里,不會打印任何東西,看起來slave進程好像永遠都在運行著。所以你可能會覺得你應該一直都能聽到”99 Bottles of Beer on the Wall”這首童謠,即使是這首歌被唱走調(diào)了也是如此,但是這首歌只有99句,而且,電腦是很快的,你假設的情況應該是不存在的,那么這個程序出了什么問題呢?
這個秘密的線索可以在Process類的文檔中找到,它敘述道:“由于某些本地平臺只提供有限大小的緩沖,所以如果未能迅速地讀取子進程(subprocess)的輸出流,就有可能會導致子進程的阻塞,甚至是死鎖” [Java-API]。這恰好就是這里所發(fā)生的事情:沒有足夠的緩沖空間來保存這首冗長的歌謠。為了確保slave進程能夠結(jié)束,父進程必須排空(drain)它的輸出流,而這個輸出流從master線程的角度來看是輸入流。下面的這個工具方法會在后臺線程中完成這項工作:
static void drainInBackground(final InputStream is) {
new Thread(new Runnable(){
public void run(){
try{
while( is.read() >= 0 );
} catch(IOException e){
// return on IOException
}
}
}).start();
}
如果我們修改原有的程序,在等待slave進程之前調(diào)用這個方法,程序就會打印出0:
}else{ // Master
Process process = Runtime.getRuntime().exec(COMMAND);
drainInBackground(process.getInputStream());
int exitValue = process.waitFor();
System.out.println("exit value = " + exitValue);
}
這里的教訓是:為了確保子進程能夠結(jié)束,你必須排空它的輸出流;對于錯誤流(error stream)也是一樣,而且它可能會更麻煩,因為你無法預測進程什么時候會傾倒(dump)一些輸出到這個流中。在5.0版本中,加入了一個名為ProcessBuilder的類用于排空這些流。它的redirectErrorStream方法將各個流合并起來,所以你只需要排空這一個流。如果你決定不合并輸出流和錯誤流,你必須并行地(concurrently)排空它們。試圖順序化地(sequentially)排空它們會導致子進程被掛起。
多年以來,很多程序員都被這個缺陷所刺痛。這里對于API設計者們的教訓是,Process類應該避免這個錯誤,也許應該自動地排空輸出流和錯誤流,除非用戶表示要讀取它們。更一般的講,API應該設計得更容易做出正確的事,而很難或不可能做出錯誤的事
public class BeerBlast{
static final String COMMAND = "java BeerBlast slave";
public static void main(String[] args) throws Exception{
if(args.length == 1 && args[0].equals("slave")) {
for(int i = 99; i > 0; i--){
System.out.println( i +
" bottles of beer on the wall" );
System.out.println(i + " bottles of beer");
System.out.println(
"You take on down, pass it around,");
System.out.println( (i-1) +
" bottles of beer on the wall");
System.out.println();
}
}else{
// Master
Process process = Runtime.getRuntime().exec(COMMAND);
int exitValue = process.waitFor();
System.out.println("exit value = " + exitValue);
}
}
}
如果你使用參數(shù)slave來運行該程序,它就會打印出那首激動人心的名為”99 Bottles of Beer on the Wall”的童謠的歌詞,這沒有什么神秘的。如果你不使用該參數(shù)來運行這個程序,它會啟動一個slave進程來打印這首歌謠,但是你看不到slave進程的輸出。主進程會等待slave進程結(jié)束,然后打印出slave進程的退出值(exit value)。根據(jù)慣例,0值表示正常結(jié)束,所以0就是你可能期望該程序打印的東西。如果你運行了程序,你可能會發(fā)現(xiàn)該程序只會懸掛在那里,不會打印任何東西,看起來slave進程好像永遠都在運行著。所以你可能會覺得你應該一直都能聽到”99 Bottles of Beer on the Wall”這首童謠,即使是這首歌被唱走調(diào)了也是如此,但是這首歌只有99句,而且,電腦是很快的,你假設的情況應該是不存在的,那么這個程序出了什么問題呢?
這個秘密的線索可以在Process類的文檔中找到,它敘述道:“由于某些本地平臺只提供有限大小的緩沖,所以如果未能迅速地讀取子進程(subprocess)的輸出流,就有可能會導致子進程的阻塞,甚至是死鎖” [Java-API]。這恰好就是這里所發(fā)生的事情:沒有足夠的緩沖空間來保存這首冗長的歌謠。為了確保slave進程能夠結(jié)束,父進程必須排空(drain)它的輸出流,而這個輸出流從master線程的角度來看是輸入流。下面的這個工具方法會在后臺線程中完成這項工作:
static void drainInBackground(final InputStream is) {
new Thread(new Runnable(){
public void run(){
try{
while( is.read() >= 0 );
} catch(IOException e){
// return on IOException
}
}
}).start();
}
如果我們修改原有的程序,在等待slave進程之前調(diào)用這個方法,程序就會打印出0:
}else{ // Master
Process process = Runtime.getRuntime().exec(COMMAND);
drainInBackground(process.getInputStream());
int exitValue = process.waitFor();
System.out.println("exit value = " + exitValue);
}
這里的教訓是:為了確保子進程能夠結(jié)束,你必須排空它的輸出流;對于錯誤流(error stream)也是一樣,而且它可能會更麻煩,因為你無法預測進程什么時候會傾倒(dump)一些輸出到這個流中。在5.0版本中,加入了一個名為ProcessBuilder的類用于排空這些流。它的redirectErrorStream方法將各個流合并起來,所以你只需要排空這一個流。如果你決定不合并輸出流和錯誤流,你必須并行地(concurrently)排空它們。試圖順序化地(sequentially)排空它們會導致子進程被掛起。
多年以來,很多程序員都被這個缺陷所刺痛。這里對于API設計者們的教訓是,Process類應該避免這個錯誤,也許應該自動地排空輸出流和錯誤流,除非用戶表示要讀取它們。更一般的講,API應該設計得更容易做出正確的事,而很難或不可能做出錯誤的事