14.5 回顾runnable
14.5 回顾 runnable
14.5 回顾 runnable
在本章早些时候,我曾建议大家在将一个程序片或主 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 机制在速度上造成的戏剧性的差异。
14.5.1 过多的线程
有些时候,我们会发现 ColorBoxes 几乎陷于停顿状态。在我自己的机器上,这一情况在产生了 10×10 的网格之后发生了。为什么会这样呢?自然地,我们有理由怀疑 AWT 对它做了什么事情。所以这里有一个例子能够测试那个猜测,它产生了较少的线程。代码经过了重新组织,使一个 Vector 实现了 Runnable,而且那个 Vector 容纳了数量众多的色块,并随机挑选一些进行更新。随后,我们创建大量这些 Vector 对象,数量大致取决于我们挑选的网格维数。结果便是我们得到比色块少得多的线程。所以假如有一个速度的加快,我们就能立即知道,因为前例的线程数量太多了。如下所示:
//: ColorBoxes2.java
// Balancing thread use
import java.awt.*;
import java.awt.event.*;
import java.util.*;
class CBox2 extends Canvas {
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)
];
}
void nextColor() {
cColor = newColor();
repaint();
}
public void paint(Graphics g) {
g.setColor(cColor);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
}
}
class CBoxVector
extends Vector implements Runnable {
private Thread t;
private int pause;
public CBoxVector(int pause) {
this.pause = pause;
t = new Thread(this);
}
public void go() { t.start(); }
public void run() {
while(true) {
int i = (int)(Math.random() * size());
((CBox2)elementAt(i)).nextColor();
try {
t.sleep(pause);
} catch(InterruptedException e) {}
}
}
}
public class ColorBoxes2 extends Frame {
private CBoxVector[] v;
public ColorBoxes2(int pause, int grid) {
setTitle("ColorBoxes2");
setLayout(new GridLayout(grid, grid));
v = new CBoxVector[grid];
for(int i = 0; i < grid; i++)
v[i] = new CBoxVector(pause);
for (int i = 0; i < grid * grid; i++) {
v[i % grid].addElement(new CBox2());
add((CBox2)v[i % grid].lastElement());
}
for(int i = 0; i < grid; i++)
v[i].go();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String[] args) {
// Shorter default pause than ColorBoxes:
int pause = 5;
int grid = 8;
if(args.length > 0)
pause = Integer.parseInt(args[0]);
if(args.length > 1)
grid = Integer.parseInt(args[1]);
Frame f = new ColorBoxes2(pause, grid);
f.setSize(500, 400);
f.setVisible(true);
}
} ///:~
在 ColorBoxes2 中,我们创建了 CBoxVector 的一个数组,并对其初始化,使其容下各个 CBoxVector 网格。每个网格都知道自己该“睡眠”多长的时间。随后为每个 CBoxVector 都添加等量的 Cbox2 对象,而且将每个 Vector 都告诉给 go(),用它来启动自己的线程。
CBox2 类似 CBox——能用一种随机选择的颜色描绘自己。但那就是 CBox2 能够做的全部工作。所有涉及线程的处理都已移至 CBoxVector 进行。
CBoxVector 也可以拥有继承的 Thread,并有一个类型为 Vector 的成员对象。这样设计的好处就是 addElement()和 elementAt()方法可以获得特定的参数以及返回值类型,而不是只能获得常规 Object(它们的名字也可以变得更短)。然而,这里采用的设计表面上看需要较少的代码。除此以外,它会自动保留一个 Vector 的其他所有行为。由于 elementAt()需要大量进行“封闭”工作,用到许多括号,所以随着代码主体的扩充,最终仍有可能需要大量代码。
和以前一样,在我们实现 Runnable 的时候,并没有获得与 Thread 配套提供的所有功能,所以必须创建一个新的 Thread,并将自己传递给它的构造器,以便正式“启动”——start()——一些东西。大家在 CBoxVector 构造器和 go()里都可以体会到这一点。run()方法简单地选择 Vector 里的一个随机元素编号,并为那个元素调用 nextColor(),令其挑选一种新的随机颜色。
运行这个程序时,大家会发现它确实变得更快,响应也更迅速(比如在中断它的时候,它能更快地停下来)。而且随着网格尺寸的壮 大,它也不会经常性地陷于“停顿”状态。因此,线程的处理又多了一项新的考虑因素:必须随时检查自己有没有“太多的线程”(无论对什么程序和运行平台)。若线程太多,必须试着使用上面介绍的技术,对程序中的线程数量进行“平衡”。如果在一个多线程的程序中遇到了性能上的问题,那么现在有许多因素需要检查:
(1) 对 sleep,yield()以及/或者 wait()的调用足够多吗?
(2) sleep()的调用时间足够长吗?
(3) 运行的线程数是不是太多?
(4) 试过不同的平台和 JVM 吗?
象这样的一些问题是造成多线程应用程序的编制成为一种“技术活”的原因之一。