# 14.4 優先級
線程的優先級(Priority)告訴調試程序該線程的重要程度有多大。如果有大量線程都被堵塞,都在等候運行,調試程序會首先運行具有最高優先級的那個線程。然而,這并不表示優先級較低的線程不會運行(換言之,不會因為存在優先級而導致死鎖)。若線程的優先級較低,只不過表示它被準許運行的機會小一些而已。
可用`getPriority()`方法讀取一個線程的優先級,并用`setPriority()`改變它。在下面這個程序片中,大家會發現計數器的計數速度慢了下來,因為它們關聯的線程分配了較低的優先級:
```
//: Counter5.java
// Adjusting the priorities of threads
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class Ticker2 extends Thread {
private Button
b = new Button("Toggle"),
incPriority = new Button("up"),
decPriority = new Button("down");
private TextField
t = new TextField(10),
pr = new TextField(3); // Display priority
private int count = 0;
private boolean runFlag = true;
public Ticker2(Container c) {
b.addActionListener(new ToggleL());
incPriority.addActionListener(new UpL());
decPriority.addActionListener(new DownL());
Panel p = new Panel();
p.add(t);
p.add(pr);
p.add(b);
p.add(incPriority);
p.add(decPriority);
c.add(p);
}
class ToggleL implements ActionListener {
public void actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
class UpL implements ActionListener {
public void actionPerformed(ActionEvent e) {
int newPriority = getPriority() + 1;
if(newPriority > Thread.MAX_PRIORITY)
newPriority = Thread.MAX_PRIORITY;
setPriority(newPriority);
}
}
class DownL implements ActionListener {
public void actionPerformed(ActionEvent e) {
int newPriority = getPriority() - 1;
if(newPriority < Thread.MIN_PRIORITY)
newPriority = Thread.MIN_PRIORITY;
setPriority(newPriority);
}
}
public void run() {
while (true) {
if(runFlag) {
t.setText(Integer.toString(count++));
pr.setText(
Integer.toString(getPriority()));
}
yield();
}
}
}
public class Counter5 extends Applet {
private Button
start = new Button("Start"),
upMax = new Button("Inc Max Priority"),
downMax = new Button("Dec Max Priority");
private boolean started = false;
private static final int SIZE = 10;
private Ticker2[] s = new Ticker2[SIZE];
private TextField mp = new TextField(3);
public void init() {
for(int i = 0; i < s.length; i++)
s[i] = new Ticker2(this);
add(new Label("MAX_PRIORITY = "
+ Thread.MAX_PRIORITY));
add(new Label("MIN_PRIORITY = "
+ Thread.MIN_PRIORITY));
add(new Label("Group Max Priority = "));
add(mp);
add(start);
add(upMax); add(downMax);
start.addActionListener(new StartL());
upMax.addActionListener(new UpMaxL());
downMax.addActionListener(new DownMaxL());
showMaxPriority();
// Recursively display parent thread groups:
ThreadGroup parent =
s[0].getThreadGroup().getParent();
while(parent != null) {
add(new Label(
"Parent threadgroup max priority = "
+ parent.getMaxPriority()));
parent = parent.getParent();
}
}
public void showMaxPriority() {
mp.setText(Integer.toString(
s[0].getThreadGroup().getMaxPriority()));
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(!started) {
started = true;
for(int i = 0; i < s.length; i++)
s[i].start();
}
}
}
class UpMaxL implements ActionListener {
public void actionPerformed(ActionEvent e) {
int maxp =
s[0].getThreadGroup().getMaxPriority();
if(++maxp > Thread.MAX_PRIORITY)
maxp = Thread.MAX_PRIORITY;
s[0].getThreadGroup().setMaxPriority(maxp);
showMaxPriority();
}
}
class DownMaxL implements ActionListener {
public void actionPerformed(ActionEvent e) {
int maxp =
s[0].getThreadGroup().getMaxPriority();
if(--maxp < Thread.MIN_PRIORITY)
maxp = Thread.MIN_PRIORITY;
s[0].getThreadGroup().setMaxPriority(maxp);
showMaxPriority();
}
}
public static void main(String[] args) {
Counter5 applet = new Counter5();
Frame aFrame = new Frame("Counter5");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300, 600);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
`Ticker`采用本章前面構造好的形式,但有一個額外的`TextField`(文本字段),用于顯示線程的優先級;以及兩個額外的按鈕,用于人為提高及降低優先級。
也要注意`yield()`的用法,它將控制權自動返回給調試程序(機制)。若不進行這樣的處理,多線程機制仍會工作,但我們會發現它的運行速度慢了下來(試試刪去對`yield()`的調用)。亦可調用`sleep()`,但假若那樣做,計數頻率就會改由`sleep()`的持續時間控制,而不是優先級。
`Counter5`中的`init()`創建了由10個`Ticker2`構成的一個數組;它們的按鈕以及輸入字段(文本字段)由`Ticker2`構造器置入窗體。`Counter5`增加了新的按鈕,用于啟動一切,以及用于提高和降低線程組的最大優先級。除此以外,還有一些標簽用于顯示一個線程可以采用的最大及最小優先級;以及一個特殊的文本字段,用于顯示線程組的最大優先級(在下一節里,我們將全面討論線程組的問題)。最后,父線程組的優先級也作為標簽顯示出來。
按下`up`(上)或`down`(下)按鈕的時候,會先取得`Ticker2`當前的優先級,然后相應地提高或者降低。
運行該程序時,我們可注意到幾件事情。首先,線程組的默認優先級是5。即使在啟動線程之前(或者在創建線程之前,這要求對代碼進行適當的修改)將最大優先級降到5以下,每個線程都會有一個5的默認優先級。
最簡單的測試是獲取一個計數器,將它的優先級降低至1,此時應觀察到它的計數頻率顯著放慢。現在試著再次提高優先級,可以升高回線程組的優先級,但不能再高了。現在將線程組的優先級降低兩次。線程的優先級不會改變,但假若試圖提高或者降低它,就會發現這個優先級自動變成線程組的優先級。此外,新線程仍然具有一個默認優先級,即使它比組的優先級還要高(換句話說,不要指望利用組優先級來防止新線程擁有比現有的更高的優先級)。
最后,試著提高組的最大優先級。可以發現,這樣做是沒有效果的。我們只能減少線程組的最大優先級,而不能增大它。
## 14.4.1 線程組
所有線程都隸屬于一個線程組。那可以是一個默認線程組,亦可是一個創建線程時明確指定的組。在創建之初,線程被限制到一個組里,而且不能改變到一個不同的組。每個應用都至少有一個線程從屬于系統線程組。若創建多個線程而不指定一個組,它們就會自動歸屬于系統線程組。
線程組也必須從屬于其他線程組。必須在構造器里指定新線程組從屬于哪個線程組。若在創建一個線程組的時候沒有指定它的歸屬,則同樣會自動成為系統線程組的一名屬下。因此,一個應用程序中的所有線程組最終都會將系統線程組作為自己的“父”。
之所以要提出“線程組”的概念,很難從字面上找到原因。這多少為我們討論的主題帶來了一些混亂。一般地說,我們認為是由于“安全”或者“保密”方面的理由才使用線程組的。根據Arnold和Gosling的說法:“線程組中的線程可以修改組內的其他線程,包括那些位于分層結構最深處的。一個線程不能修改位于自己所在組或者下屬組之外的任何線程”(注釋①)。然而,我們很難判斷“修改”在這兒的具體含義是什么。下面這個例子展示了位于一個“葉子組”內的線程能修改它所在線程組樹的所有線程的優先級,同時還能為這個“樹”內的所有線程都調用一個方法。
```
①:《The Java Programming Language》第179頁。該書由Arnold和Jams Gosling編著,Addison-Wesley于1996年出版
//: TestAccess.java
// How threads can access other threads
// in a parent thread group
public class TestAccess {
public static void main(String[] args) {
ThreadGroup
x = new ThreadGroup("x"),
y = new ThreadGroup(x, "y"),
z = new ThreadGroup(y, "z");
Thread
one = new TestThread1(x, "one"),
two = new TestThread2(z, "two");
}
}
class TestThread1 extends Thread {
private int i;
TestThread1(ThreadGroup g, String name) {
super(g, name);
}
void f() {
i++; // modify this thread
System.out.println(getName() + " f()");
}
}
class TestThread2 extends TestThread1 {
TestThread2(ThreadGroup g, String name) {
super(g, name);
start();
}
public void run() {
ThreadGroup g =
getThreadGroup().getParent().getParent();
g.list();
Thread[] gAll = new Thread[g.activeCount()];
g.enumerate(gAll);
for(int i = 0; i < gAll.length; i++) {
gAll[i].setPriority(Thread.MIN_PRIORITY);
((TestThread1)gAll[i]).f();
}
g.list();
}
} ///:~
```
在`main()`中,我們創建了幾個`ThreadGroup`(線程組),每個都位于不同的“葉”上:`x`沒有參數,只有它的名字(一個`String`),所以會自動進入`system`(系統)線程組;`y`位于`x`下方,而`z`位于`y`下方。注意初始化是按照文字順序進行的,所以代碼合法。
有兩個線程創建之后進入了不同的線程組。其中,`TestThread1`沒有一個`run()`方法,但有一個`f()`,用于通知線程以及打印出一些東西,以便我們知道它已被調用。而`TestThread2`屬于`TestThread1`的一個子類,它的`run()`非常詳盡,要做許多事情。首先,它獲得當前線程所在的線程組,然后利用`getParent()`在繼承樹中向上移動兩級(這樣做是有道理的,因為我想把`TestThread2`在分級結構中向下移動兩級)。隨后,我們調用方法`activeCount()`,查詢這個線程組以及所有子線程組內有多少個線程,從而創建由指向`Thread`的引用構成的一個數組。`enumerate()`方法將指向所有這些線程的引用置入數組`gAll`里。然后在整個數組里遍歷,為每個線程都調用`f()`方法,同時修改優先級。這樣一來,位于一個“葉子”線程組里的線程就修改了位于父線程組的線程。
調試方法`list()`打印出與一個線程組有關的所有信息,把它們作為標準輸出。在我們對線程組的行為進行調查的時候,這樣做是相當有好處的。下面是程序的輸出:
```
java.lang.ThreadGroup[name=x,maxpri=10]
Thread[one,5,x]
java.lang.ThreadGroup[name=y,maxpri=10]
java.lang.ThreadGroup[name=z,maxpri=10]
Thread[two,5,z]
one f()
two f()
java.lang.ThreadGroup[name=x,maxpri=10]
Thread[one,1,x]
java.lang.ThreadGroup[name=y,maxpri=10]
java.lang.ThreadGroup[name=z,maxpri=10]
Thread[two,1,z]
```
`list()`不僅打印出`ThreadGroup`或者`Thread`的類名,也打印出了線程組的名字以及它的最高優先級。對于線程,則打印出它們的名字,并接上線程優先級以及所屬的線程組。注意`list()`會對線程和線程組進行縮排處理,指出它們是未縮排的線程組的“子”。
大家可看到`f()`是由`TestThread2`的`run()`方法調用的,所以很明顯,組內的所有線程都是相當脆弱的。然而,我們只能訪問那些從自己的`system`線程組樹分支出來的線程,而且或許這就是所謂“安全”的意思。我們不能訪問其他任何人的系統線程樹。
(1) 線程組的控制
拋開安全問題不談,線程組最有用的一個地方就是控制:只需用單個命令即可完成對整個線程組的操作。下面這個例子演示了這一點,并對線程組內優先級的限制進行了說明。括號內的注釋數字便于大家比較輸出結果:
```
//: ThreadGroup1.java
// How thread groups control priorities
// of the threads inside them.
public class ThreadGroup1 {
public static void main(String[] args) {
// Get the system thread & print its Info:
ThreadGroup sys =
Thread.currentThread().getThreadGroup();
sys.list(); // (1)
// Reduce the system thread group priority:
sys.setMaxPriority(Thread.MAX_PRIORITY - 1);
// Increase the main thread priority:
Thread curr = Thread.currentThread();
curr.setPriority(curr.getPriority() + 1);
sys.list(); // (2)
// Attempt to set a new group to the max:
ThreadGroup g1 = new ThreadGroup("g1");
g1.setMaxPriority(Thread.MAX_PRIORITY);
// Attempt to set a new thread to the max:
Thread t = new Thread(g1, "A");
t.setPriority(Thread.MAX_PRIORITY);
g1.list(); // (3)
// Reduce g1's max priority, then attempt
// to increase it:
g1.setMaxPriority(Thread.MAX_PRIORITY - 2);
g1.setMaxPriority(Thread.MAX_PRIORITY);
g1.list(); // (4)
// Attempt to set a new thread to the max:
t = new Thread(g1, "B");
t.setPriority(Thread.MAX_PRIORITY);
g1.list(); // (5)
// Lower the max priority below the default
// thread priority:
g1.setMaxPriority(Thread.MIN_PRIORITY + 2);
// Look at a new thread's priority before
// and after changing it:
t = new Thread(g1, "C");
g1.list(); // (6)
t.setPriority(t.getPriority() -1);
g1.list(); // (7)
// Make g2 a child Threadgroup of g1 and
// try to increase its priority:
ThreadGroup g2 = new ThreadGroup(g1, "g2");
g2.list(); // (8)
g2.setMaxPriority(Thread.MAX_PRIORITY);
g2.list(); // (9)
// Add a bunch of new threads to g2:
for (int i = 0; i < 5; i++)
new Thread(g2, Integer.toString(i));
// Show information about all threadgroups
// and threads:
sys.list(); // (10)
System.out.println("Starting all threads:");
Thread[] all = new Thread[sys.activeCount()];
sys.enumerate(all);
for(int i = 0; i < all.length; i++)
if(!all[i].isAlive())
all[i].start();
// Suspends & Stops all threads in
// this group and its subgroups:
System.out.println("All threads started");
sys.suspend(); // Deprecated in Java 1.2
// Never gets here...
System.out.println("All threads suspended");
sys.stop(); // Deprecated in Java 1.2
System.out.println("All threads stopped");
}
} ///:~
```
下面的輸出結果已進行了適當的編輯,以便用一頁能夠裝下(`java.lang.`已被刪去),而且添加了適當的數字,與前面程序列表中括號里的數字對應:
```
(1) ThreadGroup[name=system,maxpri=10]
Thread[main,5,system]
(2) ThreadGroup[name=system,maxpri=9]
Thread[main,6,system]
(3) ThreadGroup[name=g1,maxpri=9]
Thread[A,9,g1]
(4) ThreadGroup[name=g1,maxpri=8]
Thread[A,9,g1]
(5) ThreadGroup[name=g1,maxpri=8]
Thread[A,9,g1]
Thread[B,8,g1]
(6) ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,6,g1]
(7) ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,3,g1]
(8) ThreadGroup[name=g2,maxpri=3]
(9) ThreadGroup[name=g2,maxpri=3]
(10)ThreadGroup[name=system,maxpri=9]
Thread[main,6,system]
ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,3,g1]
ThreadGroup[name=g2,maxpri=3]
Thread[0,6,g2]
Thread[1,6,g2]
Thread[2,6,g2]
Thread[3,6,g2]
Thread[4,6,g2]
Starting all threads:
All threads started
```
所有程序都至少有一個線程在運行,而且`main()`采取的第一項行動便是調用`Thread`的一個`static`(靜態)方法,名為`currentThread()`。從這個線程開始,線程組將被創建,而且會為結果調用`list()`。輸出如下:
```
(1) ThreadGroup[name=system,maxpri=10]
Thread[main,5,system]
```
我們可以看到,主線程組的名字是`system`,而主線程的名字是`main`,而且它從屬于`system`線程組。
第二個練習顯示出`system`組的最高優先級可以減少,而且`main`線程可以增大自己的優先級:
```
(2) ThreadGroup[name=system,maxpri=9]
Thread[main,6,system]
```
第三個練習創建一個新的線程組,名為`g1`;它自動從屬于`system`線程組,因為并沒有明確指定它的歸屬關系。我們在`g1`內部放置了一個新線程,名為`A`。隨后,我們試著將這個組的最大優先級設到最高的級別,并將`A`的優先級也設到最高一級。結果如下:
```
(3) ThreadGroup[name=g1,maxpri=9]
Thread[A,9,g1]
```
可以看出,不可能將線程組的最大優先級設為高于它的父線程組。
第四個練習將`g1`的最大優先級降低兩級,然后試著把它升至`Thread.MAX_PRIORITY`。結果如下:
```
(4) ThreadGroup[name=g1,maxpri=8]
Thread[A,9,g1]
```
同樣可以看出,提高最大優先級的企圖是失敗的。我們只能降低一個線程組的最大優先級,而不能提高它。此外,注意線程A的優先級并未改變,而且它現在高于線程組的最大優先級。也就是說,線程組最大優先級的變化并不能對現有線程造成影響。
第五個練習試著將一個新線程設為最大優先級。如下所示:
```
(5) ThreadGroup[name=g1,maxpri=8]
Thread[A,9,g1]
Thread[B,8,g1]
```
因此,新線程不能變到比最大線程組優先級還要高的一級。
這個程序的默認線程優先級是6;若新建一個線程,那就是它的默認優先級,而且不會發生變化,除非對優先級進行了特別的處理。練習六將把線程組的最大優先級降至默認線程優先級以下,看看在這種情況下新建一個線程會發生什么事情:
```
(6) ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,6,g1]
```
盡管線程組現在的最大優先級是3,但仍然用默認優先級6來創建新線程。所以,線程組的最大優先級不會影響默認優先級(事實上,似乎沒有辦法可以設置新線程的默認優先級)。
改變了優先級后,接下來試試將其降低一級,結果如下:
```
(7) ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,3,g1]
```
因此,只有在試圖改變優先級的時候,才會強迫遵守線程組最大優先級的限制。
我們在(8)和(9)中進行了類似的試驗。在這里,我們創建了一個新的線程組,名為`g2`,將其作為`g1`的一個子組,并改變了它的最大優先級。大家可以看到,`g2`的優先級無論如何都不可能高于`g1`:
```
(8) ThreadGroup[name=g2,maxpri=3]
(9) ThreadGroup[name=g2,maxpri=3]
```
也要注意在`g2`創建的時候,它會被自動設為`g1`的線程組最大優先級。
經過所有這些實驗以后,整個線程組和線程系統都會被打印出來,如下所示:
```
(10)ThreadGroup[name=system,maxpri=9]
Thread[main,6,system]
ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,3,g1]
ThreadGroup[name=g2,maxpri=3]
Thread[0,6,g2]
Thread[1,6,g2]
Thread[2,6,g2]
Thread[3,6,g2]
Thread[4,6,g2]
```
所以由線程組的規則所限,一個子組的最大優先級在任何時候都只能低于或等于它的父組的最大優先級。
本程序的最后一個部分演示了用于整組線程的方法。程序首先遍歷整個線程樹,并啟動每一個尚未啟動的線程。例如,`system`組隨后會被掛起(暫停),最后被中止(盡管用`suspend()`和`stop()`對整個線程組進行操作看起來似乎很有趣,但應注意這些方法在Java 1.2里都是被“反對”的)。但在掛起`system`組的同時,也掛起了`main`線程,而且整個程序都會關閉。所以永遠不會達到讓線程中止的那一步。實際上,假如真的中止了`main`線程,它會“拋”出一個`ThreadDeath`異常,所以我們通常不這樣做。由于`ThreadGroup`是從`Object`繼承的,其中包含了`wait()`方法,所以也能調用`wait(秒數×1000)`,令程序暫停運行任意秒數的時間。當然,事前必須在一個同步塊里取得對象鎖。
`ThreadGroup`類也提供了`suspend()`和`resume()`方法,所以能中止和啟動整個線程組和它的所有線程,也能中止和啟動它的子組,所有這些只需一個命令即可(再次提醒,`suspend()`和`resume()`都是Java 1.2所“反對”的)。
從表面看,線程組似乎有些讓人摸不著頭腦,但請注意我們很少需要直接使用它們。
- 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 推薦讀物