現在考慮一下創建多個不同的線程的問題。我們不可用前面的例子來做到這一點,所以必須倒退回去,利用從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()會深深地陷入一個無限循環——很難在多任務處理期間中止退出。因此,程序對用戶操作的反應靈敏度會大幅度降低。