在本章早些時候,我曾建議大家在將一個程序片或主Frame當作Runnable的實現形式之前,一定要好好地想一想。若采用那種方式,就只能在自己的程序中使用其中的一個線程。這便限制了靈活性,一旦需要用到屬於那種類型的多個線程,就會遇到不必要的麻煩。
當然,如果必須從一個類繼承,而且想使類具有線程處理能力,則Runnable是一種正確的方案。本章最後一個例子對這一點進行了剖析,制作了一個RunnableCanvas類,用於為自己描繪不同的顏色(Canvas是“畫布”的意思)。這個應用被設計成從命令行獲得參數值,以決定顏色網格有多大,以及顏色發生變化之間的sleep()有多長。通過運用這些值,大家能體驗到線程一些有趣而且可能令人費解的特性:
//: ColorBoxes.java // Using the Runnable interface import java.awt.*; import java.awt.event.*; class CBox extends Canvas implements Runnable { private Thread t; private int pause; private static final Color[] colors = { Color.black, Color.blue, Color.cyan, Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.magenta, Color.orange, Color.pink, Color.red, Color.white, Color.yellow }; private Color cColor = newColor(); private static final Color newColor() { return colors[ (int)(Math.random() * colors.length) ]; } public void paint(Graphics g) { g.setColor(cColor); Dimension s = getSize(); g.fillRect(0, 0, s.width, s.height); } public CBox(int pause) { this.pause = pause; t = new Thread(this); t.start(); } public void run() { while(true) { cColor = newColor(); repaint(); try { t.sleep(pause); } catch(InterruptedException e) {} } } } public class ColorBoxes extends Frame { public ColorBoxes(int pause, int grid) { setTitle("ColorBoxes"); setLayout(new GridLayout(grid, grid)); for (int i = 0; i < grid * grid; i++) add(new CBox(pause)); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public static void main(String[] args) { int pause = 50; int grid = 8; if(args.length > 0) pause = Integer.parseInt(args[0]); if(args.length > 1) grid = Integer.parseInt(args[1]); Frame f = new ColorBoxes(pause, grid); f.setSize(500, 400); f.setVisible(true); } } ///:~
ColorBoxes是一個典型的應用(程序),有一個構建器用於設置GUI。這個構建器采用int grid的一個參數,用它設置GridLayout(網格布局),使每一維裡都有一個grid單元。隨後,它添加適當數量的CBox對象,用它們填充網格,並為每一個都傳遞pause值。在main()中,我們可看到如何對pause和grid的默認值進行修改(如果用命令行參數傳遞)。
CBox是進行正式工作的地方。它是從Canvas繼承的,並實現了Runnable接口,使每個Canvas也能是一個Thread。記住在實現Runnable的時候,並沒有實際產生一個Thread對象,只是一個擁有run()方法的類。因此,我們必須明確地創建一個Thread對象,並將Runnable對象傳遞給構建器,隨後調用start()(在構建器裡進行)。在CBox裡,這個線程的名字叫作t。
請留意數組colors,它對Color類中的所有顏色進行了列舉(枚舉)。它在newColor()中用於產生一種隨機選擇的顏色。當前的單元(格)顏色是cColor。
paint()則相當簡單——只是將顏色設為cColor,然後用那種顏色填充整張畫布(Canvas)。
在run()中,我們看到一個無限循環,它將cColor設為一種隨機顏色,然後調用repaint()把它顯示出來。隨後,對線程執行sleep(),使其“休眠”由命令行指定的時間長度。
由於這種設計方案非常靈活,而且線程處理同每個Canvas元素都緊密結合在一起,所以在理論上可以生成任意多的線程(但在實際應用中,這要受到JVM能夠從容對付的線程數量的限制)。
這個程序也為我們提供了一個有趣的評測基准,因為它揭示了不同JVM機制在速度上造成的戲劇性的差異。