# 14.3 堵塞
一個線程可以有四種狀態:
(1) 新(New):線程對象已經創建,但尚未啟動,所以不可運行。
(2) 可運行(Runnable):意味著一旦時間分片機制有空閑的CPU周期提供給一個線程,那個線程便可立即開始運行。因此,線程可能在、也可能不在運行當中,但一旦條件許可,沒有什么能阻止它的運行——它既沒有“死”掉,也未被“堵塞”。
(3) 死(Dead):從自己的`run()`方法中返回后,一個線程便已“死”掉。亦可調用`stop()`令其死掉,但會產生一個異常——屬于`Error`的一個子類(也就是說,我們通常不捕獲它)。記住一個異常的“拋”出應當是一個特殊事件,而不是正常程序運行的一部分。所以不建議你使用`stop()`(在Java 1.2則是堅決反對)。另外還有一個`destroy()`方法(它永遠不會實現),應該盡可能地避免調用它,因為它非常武斷,根本不會解除對象的鎖定。
(4) 堵塞(Blocked):線程可以運行,但有某種東西阻礙了它。若線程處于堵塞狀態,調度機制可以簡單地跳過它,不給它分配任何CPU時間。除非線程再次進入“可運行”狀態,否則不會采取任何操作。
## 14.3.1 為何會堵塞
堵塞狀態是前述四種狀態中最有趣的,值得我們作進一步的探討。線程被堵塞可能是由下述五方面的原因造成的:
(1) 調用`sleep(毫秒數)`,使線程進入“睡眠”狀態。在規定的時間內,這個線程是不會運行的。
(2) 用`suspend()`暫停了線程的執行。除非線程收到`resume()`消息,否則不會返回“可運行”狀態。
(3) 用`wait()`暫停了線程的執行。除非線程收到`nofify()`或者`notifyAll()`消息,否則不會變成“可運行”(是的,這看起來同原因2非常相象,但有一個明顯的區別是我們馬上要揭示的)。
(4) 線程正在等候一些IO(輸入輸出)操作完成。
(5) 線程試圖調用另一個對象的“同步”方法,但那個對象處于鎖定狀態,暫時無法使用。
亦可調用`yield()`(`Thread`類的一個方法)自動放棄CPU,以便其他線程能夠運行。然而,假如調度機制覺得我們的線程已擁有足夠的時間,并跳轉到另一個線程,就會發生同樣的事情。也就是說,沒有什么能防止調度機制重新啟動我們的線程。線程被堵塞后,便有一些原因造成它不能繼續運行。
下面這個例子展示了進入堵塞狀態的全部五種途徑。它們全都存在于名為`Blocking.java`的一個文件中,但在這兒采用散落的片斷進行解釋(大家可注意到片斷前后的`Continued`以及`Continuing`標志。利用第17章介紹的工具,可將這些片斷連結到一起)。首先讓我們看看基本的框架:
```
//: Blocking.java
// Demonstrates the various ways a thread
// can be blocked.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.io.*;
//////////// The basic framework ///////////
class Blockable extends Thread {
private Peeker peeker;
protected TextField state = new TextField(40);
protected int i;
public Blockable(Container c) {
c.add(state);
peeker = new Peeker(this, c);
}
public synchronized int read() { return i; }
protected synchronized void update() {
state.setText(getClass().getName()
+ " state: i = " + i);
}
public void stopPeeker() {
// peeker.stop(); Deprecated in Java 1.2
peeker.terminate(); // The preferred approach
}
}
class Peeker extends Thread {
private Blockable b;
private int session;
private TextField status = new TextField(40);
private boolean stop = false;
public Peeker(Blockable b, Container c) {
c.add(status);
this.b = b;
start();
}
public void terminate() { stop = true; }
public void run() {
while (!stop) {
status.setText(b.getClass().getName()
+ " Peeker " + (++session)
+ "; value = " + b.read());
try {
sleep(100);
} catch (InterruptedException e){}
}
}
} ///:Continued
```
`Blockable`類打算成為本例所有類的一個基類。一個`Blockable`對象包含了一個名為`state`的`TextField`(文本字段),用于顯示出對象有關的信息。用于顯示這些信息的方法叫作`update()`。我們發現它用`getClass.getName()`來產生類名,而不是僅僅把它打印出來;這是由于`update(0)`不知道自己為其調用的那個類的準確名字,因為那個類是從`Blockable`派生出來的。
在`Blockable`中,變動指示符是一個`int i`;派生類的`run()`方法會為其自增。
針對每個`Bloackable`對象,都會啟動`Peeker`類的一個線程。`Peeker`的任務是調用`read()`方法,檢查與自己關聯的`Blockable`對象,看看i是否發生了變化,最后用它的`status`文本字段報告檢查結果。注意`read()`和`update()`都是同步的,要求對象的鎖定能自由解除,這一點非常重要。
(1) 睡眠
這個程序的第一項測試是用`sleep()`作出的:
```
///:Continuing
///////////// Blocking via sleep() ///////////
class Sleeper1 extends Blockable {
public Sleeper1(Container c) { super(c); }
public synchronized void run() {
while(true) {
i++;
update();
try {
sleep(1000);
} catch (InterruptedException e){}
}
}
}
class Sleeper2 extends Blockable {
public Sleeper2(Container c) { super(c); }
public void run() {
while(true) {
change();
try {
sleep(1000);
} catch (InterruptedException e){}
}
}
public synchronized void change() {
i++;
update();
}
} ///:Continued
```
在`Sleeper1`中,整個`run()`方法都是同步的。我們可看到與這個對象關聯在一起的`Peeker`可以正常運行,直到我們啟動線程為止,隨后`Peeker`便會完全停止。這正是“堵塞”的一種形式:因為`Sleeper1.run()`是同步的,而且一旦線程啟動,它就肯定在`run()`內部,方法永遠不會放棄對象鎖定,造成`Peeker`線程的堵塞。
`Sleeper2`通過設置不同步的運行,提供了一種解決方案。只有`change()`方法才是同步的,所以盡管`run()`位于`sleep()`內部,`Peeker`仍然能訪問自己需要的同步方法——`read()`。在這里,我們可看到在啟動了`Sleeper2`線程以后,`Peeker`會持續運行下去。
(2) 暫停和恢復
這個例子接下來的一部分引入了“掛起”或者“暫停”(`Suspend`)的概述。`Thread`類提供了一個名為`suspend()`的方法,可臨時中止線程;以及一個名為`resume()`的方法,用于從暫停處開始恢復線程的執行。顯然,我們可以推斷出`resume()`是由暫停線程外部的某個線程調用的。在這種情況下,需要用到一個名為`Resumer`(恢復器)的獨立類。演示暫停/恢復過程的每個類都有一個相關的恢復器。如下所示:
```
///:Continuing
/////////// Blocking via suspend() ///////////
class SuspendResume extends Blockable {
public SuspendResume(Container c) {
super(c);
new Resumer(this);
}
}
class SuspendResume1 extends SuspendResume {
public SuspendResume1(Container c) { super(c);}
public synchronized void run() {
while(true) {
i++;
update();
suspend(); // Deprecated in Java 1.2
}
}
}
class SuspendResume2 extends SuspendResume {
public SuspendResume2(Container c) { super(c);}
public void run() {
while(true) {
change();
suspend(); // Deprecated in Java 1.2
}
}
public synchronized void change() {
i++;
update();
}
}
class Resumer extends Thread {
private SuspendResume sr;
public Resumer(SuspendResume sr) {
this.sr = sr;
start();
}
public void run() {
while(true) {
try {
sleep(1000);
} catch (InterruptedException e){}
sr.resume(); // Deprecated in Java 1.2
}
}
} ///:Continued
```
`SuspendResume1`也提供了一個同步的`run()`方法。同樣地,當我們啟動這個線程以后,就會發現與它關聯的`Peeker`進入“堵塞”狀態,等候對象鎖被釋放,但那永遠不會發生。和往常一樣,這個問題在`SuspendResume2`里得到了解決,它并不同步整個`run()`方法,而是采用了一個單獨的同步`change()`方法。
對于Java 1.2,大家應注意`suspend()`和`resume()`已獲得強烈反對,因為`suspend()`包含了對象鎖,所以極易出現“死鎖”現象。換言之,很容易就會看到許多被鎖住的對象在傻乎乎地等待對方。這會造成整個應用程序的“凝固”。盡管在一些老程序中還能看到它們的蹤跡,但在你寫自己的程序時,無論如何都應避免。本章稍后就會講述正確的方案是什么。
(3) 等待和通知
通過前兩個例子的實踐,我們知道無論`sleep()`還是`suspend()`都不會在自己被調用的時候解除鎖定。需要用到對象鎖時,請務必注意這個問題。在另一方面,`wait()`方法在被調用時卻會解除鎖定,這意味著可在執行`wait()`期間調用線程對象中的其他同步方法。但在接著的兩個類中,我們看到`run()`方法都是“同步”的。在`wait()`期間,`Peeker`仍然擁有對同步方法的完全訪問權限。這是由于`wait()`在掛起內部調用的方法時,會解除對象的鎖定。
我們也可以看到`wait()`的兩種形式。第一種形式采用一個以毫秒為單位的參數,它具有與`sleep()`中相同的含義:暫停這一段規定時間。區別在于在`wait()`中,對象鎖已被解除,而且能夠自由地退出`wait()`,因為一個`notify()`可強行使時間流逝。
第二種形式不采用任何參數,這意味著`wait()`會持續執行,直到`notify()`介入為止。而且在一段時間以后,不會自行中止。
`wait()`和`notify()`比較特別的一個地方是這兩個方法都屬于基類`Object`的一部分,不象`sleep()`,`suspend()`以及`resume()`那樣屬于`Thread`的一部分。盡管這表面看有點兒奇怪——居然讓專門進行線程處理的東西成為通用基類的一部分——但仔細想想又會釋然,因為它們操縱的對象鎖也屬于每個對象的一部分。因此,我們可將一個`wait()`置入任何同步方法內部,無論在那個類里是否準備進行涉及線程的處理。事實上,我們能調用`wait()`的唯一地方是在一個同步的方法或代碼塊內部。若在一個不同步的方法內調用`wait()`或者`notify()`,盡管程序仍然會編譯,但在運行它的時候,就會得到一個`IllegalMonitorStateException`(非法監視器狀態異常),而且會出現多少有點莫名其妙的一條消息:`current thread not owner`(當前線程不是所有人”。注意`sleep()`,`suspend()`以及`resume()`都能在不同步的方法內調用,因為它們不需要對鎖定進行操作。
只能為自己的鎖定調用`wait()`和`notify()`。同樣地,仍然可以編譯那些試圖使用錯誤鎖定的代碼,但和往常一樣會產生同樣的`IllegalMonitorStateException`異常。我們沒辦法用其他人的對象鎖來愚弄系統,但可要求另一個對象執行相應的操作,對它自己的鎖進行操作。所以一種做法是創建一個同步方法,令其為自己的對象調用`notify()`。但在`Notifier`中,我們會看到一個同步方法內部的`notify()`:
```
synchronized(wn2) {
wn2.notify();
}
```
其中,`wn2`是類型為`WaitNotify2`的對象。盡管并不屬于`WaitNotify2`的一部分,這個方法仍然獲得了`wn2`對象的鎖定。在這個時候,它為`wn2`調用`notify()`是合法的,不會得到`IllegalMonitorStateException`異常。
```
///:Continuing
/////////// Blocking via wait() ///////////
class WaitNotify1 extends Blockable {
public WaitNotify1(Container c) { super(c); }
public synchronized void run() {
while(true) {
i++;
update();
try {
wait(1000);
} catch (InterruptedException e){}
}
}
}
class WaitNotify2 extends Blockable {
public WaitNotify2(Container c) {
super(c);
new Notifier(this);
}
public synchronized void run() {
while(true) {
i++;
update();
try {
wait();
} catch (InterruptedException e){}
}
}
}
class Notifier extends Thread {
private WaitNotify2 wn2;
public Notifier(WaitNotify2 wn2) {
this.wn2 = wn2;
start();
}
public void run() {
while(true) {
try {
sleep(2000);
} catch (InterruptedException e){}
synchronized(wn2) {
wn2.notify();
}
}
}
} ///:Continued
```
若必須等候其他某些條件(從線程外部加以控制)發生變化,同時又不想在線程內一直傻乎乎地等下去,一般就需要用到`wait()`。`wait()`允許我們將線程置入“睡眠”狀態,同時又“積極”地等待條件發生改變。而且只有在一個`notify()`或`notifyAll()`發生變化的時候,線程才會被喚醒,并檢查條件是否有變。因此,我們認為它提供了在線程間進行同步的一種手段。
(4) IO堵塞
若一個數據流必須等候一些IO活動,便會自動進入“堵塞”狀態。在本例下面列出的部分中,有兩個類協同通用的`Reader`以及`Writer`對象工作(使用Java 1.1的流)。但在測試模型中,會設置一個管道化的數據流,使兩個線程相互間能安全地傳遞數據(這正是使用管道流的目的)。
`Sender`將數據置入`Writer`,并“睡眠”隨機長短的時間。然而,`Receiver`本身并沒有包括`sleep()`,`suspend()`或者`wait()`方法。但在執行`read()`的時候,如果沒有數據存在,它會自動進入“堵塞”狀態。如下所示:
```
///:Continuing
class Sender extends Blockable { // send
private Writer out;
public Sender(Container c, Writer out) {
super(c);
this.out = out;
}
public void run() {
while(true) {
for(char c = 'A'; c <= 'z'; c++) {
try {
i++;
out.write(c);
state.setText("Sender sent: "
+ (char)c);
sleep((int)(3000 * Math.random()));
} catch (InterruptedException e){}
catch (IOException e) {}
}
}
}
}
class Receiver extends Blockable {
private Reader in;
public Receiver(Container c, Reader in) {
super(c);
this.in = in;
}
public void run() {
try {
while(true) {
i++; // Show peeker it's alive
// Blocks until characters are there:
state.setText("Receiver read: "
+ (char)in.read());
}
} catch(IOException e) { e.printStackTrace();}
}
} ///:Continued
```
這兩個類也將信息送入自己的`state`字段,并修改`i`值,使`Peeker`知道線程仍在運行。
(5) 測試
令人驚訝的是,主要的程序片(Applet)類非常簡單,這是大多數工作都已置入`Blockable`框架的緣故。大概地說,我們創建了一個由`Blockable`對象構成的數組。而且由于每個對象都是一個線程,所以在按下`"start"`按鈕后,它們會采取自己的行動。還有另一個按鈕和`actionPerformed()`從句,用于中止所有`Peeker`對象。由于Java 1.2“反對”使用`Thread`的`stop()`方法,所以可考慮采用這種折衷形式的中止方式。
為了在`Sender`和`Receiver`之間建立一個連接,我們創建了一個`PipedWriter`和一個`PipedReader`。注意`PipedReader in`必須通過一個構造器參數同`PipedWriterout`連接起來。在那以后,我們在`out`內放進去的所有東西都可從`in`中提取出來——似乎那些東西是通過一個“管道”傳輸過去的。隨后將`in`和`out`對象分別傳遞給`Receiver`和`Sender`構造器;后者將它們當作任意類型的`Reader`和`Writer`看待(也就是說,它們被“上溯”轉換了)。
`Blockable`引用`b`的數組在定義之初并未得到初始化,因為管道化的數據流是不可在定義前設置好的(對`try`塊的需要將成為障礙):
```
///:Continuing
/////////// Testing Everything ///////////
public class Blocking extends Applet {
private Button
start = new Button("Start"),
stopPeekers = new Button("Stop Peekers");
private boolean started = false;
private Blockable[] b;
private PipedWriter out;
private PipedReader in;
public void init() {
out = new PipedWriter();
try {
in = new PipedReader(out);
} catch(IOException e) {}
b = new Blockable[] {
new Sleeper1(this),
new Sleeper2(this),
new SuspendResume1(this),
new SuspendResume2(this),
new WaitNotify1(this),
new WaitNotify2(this),
new Sender(this, out),
new Receiver(this, in)
};
start.addActionListener(new StartL());
add(start);
stopPeekers.addActionListener(
new StopPeekersL());
add(stopPeekers);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(!started) {
started = true;
for(int i = 0; i < b.length; i++)
b[i].start();
}
}
}
class StopPeekersL implements ActionListener {
public void actionPerformed(ActionEvent e) {
// Demonstration of the preferred
// alternative to Thread.stop():
for(int i = 0; i < b.length; i++)
b[i].stopPeeker();
}
}
public static void main(String[] args) {
Blocking applet = new Blocking();
Frame aFrame = new Frame("Blocking");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(350,550);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
在`init()`中,注意循環會遍歷整個數組,并為頁添加`state`和`peeker.status`文本字段。
首次創建好`Blockable`線程以后,每個這樣的線程都會自動創建并啟動自己的`Peeker`。所以我們會看到各個`Peeker`都在`Blockable`線程啟動之前運行起來。這一點非常重要,因為在`Blockable`線程啟動的時候,部分`Peeker`會被堵塞,并停止運行。弄懂這一點,將有助于我們加深對“堵塞”這一概念的認識。
## 14.3.2 死鎖
由于線程可能進入堵塞狀態,而且由于對象可能擁有“同步”方法——除非同步鎖定被解除,否則線程不能訪問那個對象——所以一個線程完全可能等候另一個對象,而另一個對象又在等候下一個對象,以此類推。這個“等候”鏈最可怕的情形就是進入封閉狀態——最后那個對象等候的是第一個對象!此時,所有線程都會陷入無休止的相互等待狀態,大家都動彈不得。我們將這種情況稱為“死鎖”。盡管這種情況并非經常出現,但一旦碰到,程序的調試將變得異常艱難。
就語言本身來說,尚未直接提供防止死鎖的幫助措施,需要我們通過謹慎的設計來避免。如果有誰需要調試一個死鎖的程序,他是沒有任何竅門可用的。
(1) Java 1.2對`stop()`,`suspend()`,`resume()`以及`destroy()`的反對
為減少出現死鎖的可能,Java 1.2作出的一項貢獻是“反對”使用`Thread`的`stop()`,`suspend()`,`resume()`以及`destroy()`方法。
之所以反對使用`stop()`,是因為它不安全。它會解除由線程獲取的所有鎖定,而且如果對象處于一種不連貫狀態(“被析構”),那么其他線程能在那種狀態下檢查和修改它們。結果便造成了一種微妙的局面,我們很難檢查出真正的問題所在。所以應盡量避免使用`stop()`,應該采用`Blocking.java`那樣的方法,用一個標志告訴線程什么時候通過退出自己的`run()`方法來中止自己的執行。
如果一個線程被堵塞,比如在它等候輸入的時候,那么一般都不能象在`Blocking.java`中那樣輪詢一個標志。但在這些情況下,我們仍然不該使用`stop()`,而應換用由`Thread`提供的`interrupt()`方法,以便中止并退出堵塞的代碼。
```
//: Interrupt.java
// The alternative approach to using stop()
// when a thread is blocked
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class Blocked extends Thread {
public synchronized void run() {
try {
wait(); // Blocks
} catch(InterruptedException e) {
System.out.println("InterruptedException");
}
System.out.println("Exiting run()");
}
}
public class Interrupt extends Applet {
private Button
interrupt = new Button("Interrupt");
private Blocked blocked = new Blocked();
public void init() {
add(interrupt);
interrupt.addActionListener(
new ActionListener() {
public
void actionPerformed(ActionEvent e) {
System.out.println("Button pressed");
if(blocked == null) return;
Thread remove = blocked;
blocked = null; // to release it
remove.interrupt();
}
});
blocked.start();
}
public static void main(String[] args) {
Interrupt applet = new Interrupt();
Frame aFrame = new Frame("Interrupt");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(200,100);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
`Blocked.run()`內部的`wait()`會產生堵塞的線程。當我們按下按鈕以后,`blocked`(堵塞)的引用就會設為`null`,使垃圾收集器能夠將其清除,然后調用對象的`interrupt()`方法。如果是首次按下按鈕,我們會看到線程正常退出。但在沒有可供“殺死”的線程以后,看到的便只是按鈕被按下而已。
`suspend()`和`resume()`方法天生容易發生死鎖。調用`suspend()`的時候,目標線程會停下來,但卻仍然持有在這之前獲得的鎖定。此時,其他任何線程都不能訪問鎖定的資源,除非被“掛起”的線程恢復運行。對任何線程來說,如果它們想恢復目標線程,同時又試圖使用任何一個鎖定的資源,就會造成令人難堪的死鎖。所以我們不應該使用`suspend()`和`resume()`,而應在自己的`Thread`類中置入一個標志,指出線程應該活動還是掛起。若標志指出線程應該掛起,便用`wait()`命其進入等待狀態。若標志指出線程應當恢復,則用一個`notify()`重新啟動線程。我們可以修改前面的`Counter2.java`來實際體驗一番。盡管兩個版本的效果是差不多的,但大家會注意到代碼的組織結構發生了很大的變化——為所有“聽眾”都使用了匿名的內部類,而且`Thread`是一個內部類。這使得程序的編寫稍微方便一些,因為它取消了`Counter2.java`中一些額外的記錄工作。
```
//: Suspend.java
// The alternative approach to using suspend()
// and resume(), which have been deprecated
// in Java 1.2.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Suspend extends Applet {
private TextField t = new TextField(10);
private Button
suspend = new Button("Suspend"),
resume = new Button("Resume");
class Suspendable extends Thread {
private int count = 0;
private boolean suspended = false;
public Suspendable() { start(); }
public void fauxSuspend() {
suspended = true;
}
public synchronized void fauxResume() {
suspended = false;
notify();
}
public void run() {
while (true) {
try {
sleep(100);
synchronized(this) {
while(suspended)
wait();
}
} catch (InterruptedException e){}
t.setText(Integer.toString(count++));
}
}
}
private Suspendable ss = new Suspendable();
public void init() {
add(t);
suspend.addActionListener(
new ActionListener() {
public
void actionPerformed(ActionEvent e) {
ss.fauxSuspend();
}
});
add(suspend);
resume.addActionListener(
new ActionListener() {
public
void actionPerformed(ActionEvent e) {
ss.fauxResume();
}
});
add(resume);
}
public static void main(String[] args) {
Suspend applet = new Suspend();
Frame aFrame = new Frame("Suspend");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,100);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
`Suspendable`中的`suspended`(已掛起)標志用于開關“掛起”或者“暫停”狀態。為掛起一個線程,只需調用`fauxSuspend(`)將標志設為`true`(真)即可。對標志狀態的偵測是在`run()`內進行的。就象本章早些時候提到的那樣,`wait()`必須設為“同步”(`synchronized`),使其能夠使用對象鎖。在`fauxResume()`中,`suspended`標志被設為`false`(假),并調用`notify()`——由于這會在一個“同步”從句中喚醒`wait()`,所以`fauxResume()`方法也必須同步,使其能在調用`notify()`之前取得對象鎖(這樣一來,對象鎖可由要喚醍的那個`wait()`使用)。如果遵照本程序展示的樣式,可以避免使用`wait()`和`notify()`。
`Thread`的`destroy()`方法根本沒有實現;它類似一個根本不能恢復的`suspend()`,所以會發生與`suspend()`一樣的死鎖問題。然而,這一方法沒有得到明確的“反對”,也許會在Java以后的版本(1.2版以后)實現,用于一些可以承受死鎖危險的特殊場合。
大家可能會奇怪當初為什么要實現這些現在又被“反對”的方法。之所以會出現這種情況,大概是由于Sun公司主要讓技術人員來決定對語言的改動,而不是那些市場銷售人員。通常,技術人員比搞銷售的更能理解語言的實質。當初犯下了錯誤以后,也能較為理智地正視它們。這意味著Java能夠繼續進步,即便這使Java程序員多少感到有些不便。就我自己來說,寧愿面對這些不便之處,也不愿看到語言停滯不前。
- 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 推薦讀物