在上面的例子中,我們看到線程類(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與我們的主類合並造成的,因為每個應用只能主類的一個對象。