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