# 14.1 反應靈敏的用戶界面
作為我們的起點,請思考一個需要執行某些CPU密集型計算的程序。由于CPU“全心全意”為那些計算服務,所以對用戶的輸入十分遲鈍,幾乎沒有什么反應。在這里,我們用一個組合的applet/application(程序片/應用程序)來簡單顯示出一個計數器的結果:
```
//: Counter1.java
// A non-responsive user interface
package c14;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Counter1 extends Applet {
private int count = 0;
private Button
onOff = new Button("Toggle"),
start = new Button("Start");
private TextField t = new TextField(10);
private boolean runFlag = true;
public void init() {
add(t);
start.addActionListener(new StartL());
add(start);
onOff.addActionListener(new OnOffL());
add(onOff);
}
public void go() {
while (true) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e){}
if(runFlag)
t.setText(Integer.toString(count++));
}
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
go();
}
}
class OnOffL implements ActionListener {
public void actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
public static void main(String[] args) {
Counter1 applet = new Counter1();
Frame aFrame = new Frame("Counter1");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
在這個程序中,AWT和程序片代碼都應是大家熟悉的,第13章對此已有很詳細的交待。`go()`方法正是程序全心全意服務的對待:將當前的`count`(計數)值置入`TextField`(文本字段)`t`,然后使`count`自增。
`go()`內的部分無限循環是調用`sleep()`。`sleep()`必須同一個`Thread`(線程)對象關聯到一起,而且似乎每個應用程序都有部分線程同它關聯(事實上,Java本身就是建立在線程基礎上的,肯定有一些線程會伴隨我們寫的應用一起運行)。所以無論我們是否明確使用了線程,都可利用`Thread.currentThread()`產生由程序使用的當前線程,然后為那個線程調用`sleep()`。注意,`Thread.currentThread()`是`Thread`類的一個靜態方法。
注意`sleep()`可能“拋”出一個`InterruptException`(中斷異常)——盡管產生這樣的異常被認為是中止線程的一種“惡意”手段,而且應該盡可能地杜絕這一做法。再次提醒大家,異常是為異常情況而產生的,而不是為了正常的控制流。在這里包含了對一個“睡眠”線程的中斷,以支持未來的一種語言特性。
一旦按下`start`按鈕,就會調用`go()`。研究一下`go()`,你可能會很自然地(就象我一樣)認為它該支持多線程,因為它會進入“睡眠”狀態。也就是說,盡管方法本身“睡著”了,CPU仍然應該忙于監視其他按鈕“按下”事件。但有一個問題,那就是`go()`是永遠不會返回的,因為它被設計成一個無限循環。這意味著`actionPerformed()`根本不會返回。由于在第一個按鍵以后便陷入`actionPerformed()`中,所以程序不能再對其他任何事件進行控制(如果想出來,必須以某種方式“殺死”進程——最簡便的方式就是在控制臺窗口按`Ctrl+C`鍵)。
這里最基本的問題是`go()`需要繼續執行自己的操作,而與此同時,它也需要返回,以便`actionPerformed()`能夠完成,而且用戶界面也能繼續響應用戶的操作。但對象`go()`這樣的傳統方法來說,它卻不能在繼續的同時將控制權返回給程序的其他部分。這聽起來似乎是一件不可能做到的事情,就象CPU必須同時位于兩個地方一樣,但線程可以解決一切。“線程模型”(以及Java中的編程支持)是一種程序編寫規范,可在單獨一個程序里實現幾個操作的同時進行。根據這一機制,CPU可為每個線程都分配自己的一部分時間。每個線程都“感覺”自己好象擁有整個CPU,但CPU的計算時間實際卻是在所有線程間分攤的。
線程機制多少降低了一些計算效率,但無論程序的設計,資源的均衡,還是用戶操作的方便性,都從中獲得了巨大的利益。綜合考慮,這一機制是非常有價值的。當然,如果本來就安裝了多塊CPU,那么操作系統能夠自行決定為不同的CPU分配哪些線程,程序的總體運行速度也會變得更快(所有這些都要求操作系統以及應用程序的支持)。多線程和多任務是充分發揮多處理機系統能力的一種最有效的方式。
## 14.1.1 從線程繼承
為創建一個線程,最簡單的方法就是從`Thread`類繼承。這個類包含了創建和運行線程所需的一切東西。`Thread`最重要的方法是`run()`。但為了使用`run()`,必須對其進行重載或者覆蓋,使其能充分按自己的吩咐行事。因此,`run()`屬于那些會與程序中的其他線程“并發”或“同時”執行的代碼。
下面這個例子可創建任意數量的線程,并通過為每個線程分配一個獨一無二的編號(由一個靜態變量產生),從而對不同的線程進行跟蹤。`Thread`的`run()`方法在這里得到了覆蓋,每通過一次循環,計數就減1——計數為0時則完成循環(此時一旦返回run(),線程就中止運行)。
```
//: SimpleThread.java
// Very simple Threading example
public class SimpleThread extends Thread {
private int countDown = 5;
private int threadNumber;
private static int threadCount = 0;
public SimpleThread() {
threadNumber = ++threadCount;
System.out.println("Making " + threadNumber);
}
public void run() {
while(true) {
System.out.println("Thread " +
threadNumber + "(" + countDown + ")");
if(--countDown == 0) return;
}
}
public static void main(String[] args) {
for(int i = 0; i < 5; i++)
new SimpleThread().start();
System.out.println("All Threads Started");
}
} ///:~
```
`run()`方法幾乎肯定含有某種形式的循環——它們會一直持續到線程不再需要為止。因此,我們必須規定特定的條件,以便中斷并退出這個循環(或者在上述的例子中,簡單地從`run()`返回即可)。`run()`通常采用一種無限循環的形式。也就是說,通過阻止外部發出對線程的`stop()`或者`destroy()`調用,它會永遠運行下去(直到程序完成)。
在`main()`中,可看到創建并運行了大量線程。`Thread`包含了一個特殊的方法,叫作`start()`,它的作用是對線程進行特殊的初始化,然后調用`run()`。所以整個步驟包括:調用構造器來構建對象,然后用`start()`配置線程,再調用`run()`。如果不調用`start()`——如果適當的話,可在構造器那樣做——線程便永遠不會啟動。
下面是該程序某一次運行的輸出(注意每次運行都會不同):
```
Making 1
Making 2
Making 3
Making 4
Making 5
Thread 1(5)
Thread 1(4)
Thread 1(3)
Thread 1(2)
Thread 2(5)
Thread 2(4)
Thread 2(3)
Thread 2(2)
Thread 2(1)
Thread 1(1)
All Threads Started
Thread 3(5)
Thread 4(5)
Thread 4(4)
Thread 4(3)
Thread 4(2)
Thread 4(1)
Thread 5(5)
Thread 5(4)
Thread 5(3)
Thread 5(2)
Thread 5(1)
Thread 3(4)
Thread 3(3)
Thread 3(2)
Thread 3(1)
```
可注意到這個例子中到處都調用了`sleep()`,然而輸出結果指出每個線程都獲得了屬于自己的那一部分CPU執行時間。從中可以看出,盡管`sleep()`依賴一個線程的存在來執行,但卻與允許或禁止線程無關。它只不過是另一個不同的方法而已。
亦可看出線程并不是按它們創建時的順序運行的。事實上,CPU處理一個現有線程集的順序是不確定的——除非我們親自介入,并用`Thread`的`setPriority()`方法調整它們的優先級。
`main()`創建`Thread`對象時,它并未捕獲任何一個對象的引用。普通對象對于垃圾收集來說是一種“公平競賽”,但線程卻并非如此。每個線程都會“注冊”自己,所以某處實際存在著對它的一個引用。這樣一來,垃圾收集器便只好對它“瞠目以對”了。
## 14.1.2 針對用戶界面的多線程
現在,我們也許能用一個線程解決在`Counter1.java`中出現的問題。采用的一個技巧便是在一個線程的`run()`方法中放置“子任務”——亦即位于`go()`內的循環。一旦用戶按下`Start`按鈕,線程就會啟動,但馬上結束線程的創建。這樣一來,盡管線程仍在運行,但程序的主要工作卻能得以繼續(等候并響應用戶界面的事件)。下面是具體的代碼:
```
//: Counter2.java
// A responsive user interface with threads
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class SeparateSubTask extends Thread {
private int count = 0;
private Counter2 c2;
private boolean runFlag = true;
public SeparateSubTask(Counter2 c2) {
this.c2 = c2;
start();
}
public void invertFlag() { runFlag = !runFlag;}
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e){}
if(runFlag)
c2.t.setText(Integer.toString(count++));
}
}
}
public class Counter2 extends Applet {
TextField t = new TextField(10);
private SeparateSubTask sp = null;
private Button
onOff = new Button("Toggle"),
start = new Button("Start");
public void init() {
add(t);
start.addActionListener(new StartL());
add(start);
onOff.addActionListener(new OnOffL());
add(onOff);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp == null)
sp = new SeparateSubTask(Counter2.this);
}
}
class OnOffL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp != null)
sp.invertFlag();
}
}
public static void main(String[] args) {
Counter2 applet = new Counter2();
Frame aFrame = new Frame("Counter2");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
現在,`Counter2`變成了一個相當直接的程序,它的唯一任務就是設置并管理用戶界面。但假若用戶現在按下`Start`按鈕,卻不會真正調用一個方法。此時不是創建類的一個線程,而是創建`SeparateSubTask`,然后繼續`Counter2`事件循環。注意此時會保存`SeparateSubTask`的引用,以便我們按下`onOff`按鈕的時候,能正常地切換位于`SeparateSubTask`內部的`runFlag`(運行標志)。隨后那個線程便可啟動(當它看到標志的時候),然后將自己中止(亦可將`SeparateSubTask`設為一個內部類來達到這一目的)。
`SeparateSubTask`類是對`Thread`的一個簡單擴展,它帶有一個構造器(其中保存了`Counter2`引用,然后通過調用`start()`來運行線程)以及一個`run()`——本質上包含了`Counter1.java`的`go()`內的代碼。由于`SeparateSubTask`知道自己容納了指向一個`Counter2`的引用,所以能夠在需要的時候介入,并訪問`Counter2`的`TestField`(文本字段)。
按下`onOff`按鈕,幾乎立即能得到正確的響應。當然,這個響應其實并不是“立即”發生的,它畢竟和那種由“中斷”驅動的系統不同。只有線程擁有CPU的執行時間,并注意到標記已發生改變,計數器才會停止。
(1) 用內部類改善代碼
下面說說題外話,請大家注意一下`SeparateSubTask`和`Counter2`類之間發生的結合行為。`SeparateSubTask`同`Counter2`“親密”地結合到了一起——它必須持有指向自己“父”`Counter2`對象的一個引用,以便自己能回調和操縱它。但兩個類并不是真的合并為單獨一個類(盡管在下一節中,我們會講到Java確實提供了合并它們的方法),因為它們各自做的是不同的事情,而且是在不同的時間創建的。但不管怎樣,它們依然緊密地結合到一起(更準確地說,應該叫“聯合”),所以使程序代碼多少顯得有些笨拙。在這種情況下,一個內部類可以顯著改善代碼的“可讀性”和執行效率:
```
//: Counter2i.java
// Counter2 using an inner class for the thread
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Counter2i extends Applet {
private class SeparateSubTask extends Thread {
int count = 0;
boolean runFlag = true;
SeparateSubTask() { start(); }
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e){}
if(runFlag)
t.setText(Integer.toString(count++));
}
}
}
private SeparateSubTask sp = null;
private TextField t = new TextField(10);
private Button
onOff = new Button("Toggle"),
start = new Button("Start");
public void init() {
add(t);
start.addActionListener(new StartL());
add(start);
onOff.addActionListener(new OnOffL());
add(onOff);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp == null)
sp = new SeparateSubTask();
}
}
class OnOffL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp != null)
sp.runFlag = !sp.runFlag; // invertFlag();
}
}
public static void main(String[] args) {
Counter2i applet = new Counter2i();
Frame aFrame = new Frame("Counter2i");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
這個`SeparateSubTask`名字不會與前例中的`SeparateSubTask`沖突——即使它們都在相同的目錄里——因為它已作為一個內部類隱藏起來。大家亦可看到內部類被設為`private`(私有)屬性,這意味著它的字段和方法都可獲得默認的訪問權限(`run()`除外,它必須設為`public`,因為它在基類中是公開的)。除`Counter2i`之外,其他任何方面都不可訪問`private`內部類。而且由于兩個類緊密結合在一起,所以很容易放寬它們之間的訪問限制。在`SeparateSubTask`中,我們可看到`invertFlag()`方法已被刪去,因為`Counter2i`現在可以直接訪問`runFlag`。
此外,注意`SeparateSubTask`的構造器已得到了簡化——它現在唯一的用外就是啟動線程。`Counter2i`對象的引用仍象以前那樣得以捕獲,但不再是通過人工傳遞和引用外部對象來達到這一目的,此時的內部類機制可以自動照料它。在`run()`中,可看到對`t`的訪問是直接進行的,似乎它是`SeparateSubTask`的一個字段。父類中的t字段現在可以變成`private`,因為`SeparateSubTask`能在未獲任何特殊許可的前提下自由地訪問它——而且無論如何都該盡可能地把字段變成“私有”屬性,以防來自類外的某種力量不慎地改變它們。
無論在什么時候,只要注意到類相互之間結合得比較緊密,就可考慮利用內部類來改善代碼的編寫與維護。
## 14.1.3 用主類合并線程
在上面的例子中,我們看到線程類(`Thread`)與程序的主類(`Main`)是分隔開的。這樣做非常合理,而且易于理解。然而,還有另一種方式也是經常要用到的。盡管它不十分明確,但一般都要更簡潔一些(這也解釋了它為什么十分流行)。通過將主程序類變成一個線程,這種形式可將主程序類與線程類合并到一起。由于對一個GUI程序來說,主程序類必須從`Frame`或`Applet`繼承,所以必須用一個接口加入額外的功能。這個接口叫作`Runnable`,其中包含了與`Thread`一致的基本方法。事實上,`Thread`也實現了`Runnable`,它只指出有一個`run()`方法。
對合并后的程序/線程來說,它的用法不是十分明確。當我們啟動程序時,會創建一個`Runnable`(可運行的)對象,但不會自行啟動線程。線程的啟動必須明確進行。下面這個程序向我們演示了這一點,它再現了`Counter2`的功能:
```
//: Counter3.java
// Using the Runnable interface to turn the
// main class into a thread.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Counter3
extends Applet implements Runnable {
private int count = 0;
private boolean runFlag = true;
private Thread selfThread = null;
private Button
onOff = new Button("Toggle"),
start = new Button("Start");
private TextField t = new TextField(10);
public void init() {
add(t);
start.addActionListener(new StartL());
add(start);
onOff.addActionListener(new OnOffL());
add(onOff);
}
public void run() {
while (true) {
try {
selfThread.sleep(100);
} catch (InterruptedException e){}
if(runFlag)
t.setText(Integer.toString(count++));
}
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(selfThread == null) {
selfThread = new Thread(Counter3.this);
selfThread.start();
}
}
}
class OnOffL implements ActionListener {
public void actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
public static void main(String[] args) {
Counter3 applet = new Counter3();
Frame aFrame = new Frame("Counter3");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
現在`run()`位于類內,但它在`init()`結束以后仍處在“睡眠”狀態。若按下啟動按鈕,線程便會用多少有些曖昧的表達方式創建(若線程尚不存在):
```
new Thread(Counter3.this);
```
若某樣東西有一個`Runnable`接口,實際只是意味著它有一個`run()`方法,但不存在與之相關的任何特殊東西——它不具有任何天生的線程處理能力,這與那些從`Thread`繼承的類是不同的。所以為了從一個`Runnable`對象產生線程,必須單獨創建一個線程,并為其傳遞`Runnable`對象;可為其使用一個特殊的構造器,并令其采用一個`Runnable`作為自己的參數使用。隨后便可為那個線程調用`start()`,如下所示:
```
selfThread.start();
```
它的作用是執行常規初始化操作,然后調用`run()`。
`Runnable`接口最大的一個優點是所有東西都從屬于相同的類。若需訪問什么東西,只需簡單地訪問它即可,不需要涉及一個獨立的對象。但為這種便利也是要付出代價的——只可為那個特定的對象運行單獨一個線程(盡管可創建那種類型的多個對象,或者在不同的類里創建其他對象)。
注意`Runnable`接口本身并不是造成這一限制的罪魁禍首。它是由于`Runnable`與我們的主類合并造成的,因為每個應用只能主類的一個對象。
## 14.1.4 制作多個線程
現在考慮一下創建多個不同的線程的問題。我們不可用前面的例子來做到這一點,所以必須倒退回去,利用從`Thread`繼承的多個獨立類來封裝`run()`。但這是一種更常規的方案,而且更易理解,所以盡管前例揭示了我們經常都能看到的編碼樣式,但并不推薦在大多數情況下都那樣做,因為它只是稍微復雜一些,而且靈活性稍低一些。
下面這個例子用計數器和切換按鈕再現了前面的編碼樣式。但這一次,一個特定計數器的所有信息(按鈕和文本字段)都位于它自己的、從`Thread`繼承的對象內。`Ticker`中的所有字段都具有`private`(私有)屬性,這意味著`Ticker`的具體實現方案可根據實際情況任意修改,其中包括修改用于獲取和顯示信息的數據組件的數量及類型。創建好一個`Ticker`對象以后,構造器便請求一個AWT容器(`Container`)的引用——`Ticker`用自己的可視組件填充那個容器。采用這種方式,以后一旦改變了可視組件,使用`Ticker`的代碼便不需要另行修改一道。
```
//: Counter4.java
// If you separate your thread from the main
// class, you can have as many threads as you
// want.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class Ticker extends Thread {
private Button b = new Button("Toggle");
private TextField t = new TextField(10);
private int count = 0;
private boolean runFlag = true;
public Ticker(Container c) {
b.addActionListener(new ToggleL());
Panel p = new Panel();
p.add(t);
p.add(b);
c.add(p);
}
class ToggleL implements ActionListener {
public void actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
public void run() {
while (true) {
if(runFlag)
t.setText(Integer.toString(count++));
try {
sleep(100);
} catch (InterruptedException e){}
}
}
}
public class Counter4 extends Applet {
private Button start = new Button("Start");
private boolean started = false;
private Ticker[] s;
private boolean isApplet = true;
private int size;
public void init() {
// Get parameter "size" from Web page:
if(isApplet)
size =
Integer.parseInt(getParameter("size"));
s = new Ticker[size];
for(int i = 0; i < s.length; i++)
s[i] = new Ticker(this);
start.addActionListener(new StartL());
add(start);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(!started) {
started = true;
for(int i = 0; i < s.length; i++)
s[i].start();
}
}
}
public static void main(String[] args) {
Counter4 applet = new Counter4();
// This isn't an applet, so set the flag and
// produce the parameter values from args:
applet.isApplet = false;
applet.size =
(args.length == 0 ? 5 :
Integer.parseInt(args[0]));
Frame aFrame = new Frame("Counter4");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(200, applet.size * 50);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
`Ticker`不僅包括了自己的線程處理機制,也提供了控制與顯示線程的工具。可按自己的意愿創建任意數量的線程,毋需明確地創建窗口化組件。
在`Counter4`中,有一個名為`s`的`Ticker`對象的數組。為獲得最大的靈活性,這個數組的長度是用程序片參數接觸Web頁而初始化的。下面是網頁中長度參數大致的樣子,它們嵌于對程序片(applet)的描述內容中:
```
<applet code=Counter4 width=600 height=600>
<param name=size value="20">
</applet>
```
其中,`param`,`name`和`value`是所有Web頁都適用的關鍵字。`name`是指程序中對參數的一種引用稱謂,`value`可以是任何字符串(并不僅僅是解析成一個數字的東西)。
我們注意到對數組`s`長度的判斷是在`init()`內部完成的,它沒有作為`s`的內嵌定義的一部分提供。換言之,不可將下述代碼作為類定義的一部分使用(應該位于任何方法的外部):
```
inst size = Integer.parseInt(getParameter("Size"));
Ticker[] s = new Ticker[size]
```
可把它編譯出來,但會在運行期得到一個空指針異常。但若將`getParameter()`初始化移入`init()`,則可正常工作。程序片框架會進行必要的啟動工作,以便在進入`init()`前收集好一些參數。
此外,上述代碼被同時設置成一個程序片和一個應用(程序)。在它是應用程序的情況下,`size`參數可從命令行里提取出來(否則就提供一個默認的值)。
數組的長度建好以后,就可以創建新的`Ticker`對象;作為`Ticker`構造器的一部分,用于每個`Ticker`的按鈕和文本字段就會加入程序片。
按下`Start`按鈕后,會在整個`Ticker`數組里遍歷,并為每個`Ticker`調用`start()`。記住,`start()`會進行必要的線程初始化工作,然后為那個線程調用`run()`。
`ToggleL`監視器只是簡單地切換`Ticker`中的標記,一旦對應線程以后需要修改這個標記,它會作出相應的反應。
這個例子的一個好處是它使我們能夠方便地創建由單獨子任務構成的大型集合,并以監視它們的行為。在這種情況下,我們會發現隨著子任務數量的增多,機器顯示出來的數字可能會出現更大的分歧,這是由于為線程提供服務的方式造成的。
亦可試著體驗一下`sleep(100)`在`Ticker.run()`中的重要作用。若刪除`sleep()`,那么在按下一個切換按鈕前,情況仍然會進展良好。按下按鈕以后,那個特定的線程就會出現一個失敗的`runFlag`,而且`run()`會深深地陷入一個無限循環——很難在多任務處理期間中止退出。因此,程序對用戶操作的反應靈敏度會大幅度降低。
## 14.1.5 Daemon線程
“Daemon”線程的作用是在程序的運行期間于后臺提供一種“常規”服務,但它并不屬于程序的一個基本部分。因此,一旦所有非Daemon線程完成,程序也會中止運行。相反,假若有任何非Daemon線程仍在運行(比如還有一個正在運行`main()`的線程),則程序的運行不會中止。
通過調用`isDaemon()`,可調查一個線程是不是一個Daemon,而且能用`setDaemon()`打開或者關閉一個線程的Daemon狀態。如果是一個Daemon線程,那么它創建的任何線程也會自動具備Daemon屬性。
下面這個例子演示了Daemon線程的用法:
```
//: Daemons.java
// Daemonic behavior
import java.io.*;
class Daemon extends Thread {
private static final int SIZE = 10;
private Thread[] t = new Thread[SIZE];
public Daemon() {
setDaemon(true);
start();
}
public void run() {
for(int i = 0; i < SIZE; i++)
t[i] = new DaemonSpawn(i);
for(int i = 0; i < SIZE; i++)
System.out.println(
"t[" + i + "].isDaemon() = "
+ t[i].isDaemon());
while(true)
yield();
}
}
class DaemonSpawn extends Thread {
public DaemonSpawn(int i) {
System.out.println(
"DaemonSpawn " + i + " started");
start();
}
public void run() {
while(true)
yield();
}
}
public class Daemons {
public static void main(String[] args) {
Thread d = new Daemon();
System.out.println(
"d.isDaemon() = " + d.isDaemon());
// Allow the daemon threads to finish
// their startup processes:
BufferedReader stdin =
new BufferedReader(
new InputStreamReader(System.in));
System.out.println("Waiting for CR");
try {
stdin.readLine();
} catch(IOException e) {}
}
} ///:~
```
Daemon線程可將自己的Daemon標記設置成“真”,然后產生一系列其他線程,而且認為它們也具有Daemon屬性。隨后,它進入一個無限循環,在其中調用`yield()`,放棄對其他進程的控制。在這個程序早期的一個版本中,無限循環會使`int`計數器自增,但會使整個程序都好象陷入停頓狀態。換用`yield()`后,卻可使程序充滿“活力”,不會使人產生停滯或反應遲鈍的感覺。
一旦`main()`完成自己的工作,便沒有什么能阻止程序中斷運行,因為這里運行的只有Daemon線程。所以能看到啟動所有Daemon線程后顯示出來的結果,`System.in`也進行了相應的設置,使程序中斷前能等待一個回車。如果不進行這樣的設置,就只能看到創建Daemon線程的一部分結果(試試將`readLine()`代碼換成不同長度的`sleep()`調用,看看會有什么表現)。
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物