按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
…………………………………………………………Page 491……………………………………………………………
Thread 1(4)
Thread 1(3)
Thread 1(2)
Thread 2(5)
Thread 2(4)
Thread 2(3)
Thread 2(2)
Thread 2(1)
Thread 1(1)
All Threads Started
Thread 3(5)
Thread 4(5)
Thread 4(4)
Thread 4(3)
Thread 4(2)
Thread 4(1)
Thread 5(5)
Thread 5(4)
Thread 5(3)
Thread 5(2)
Thread 5(1)
Thread 3(4)
Thread 3(3)
Thread 3(2)
Thread 3(1)
可注意到这个例子中到处都调用了 sleep(),然而输出结果指出每个线程都获得了属于自己的那一部分CPU
执行时间。从中可以看出,尽管 sleep()依赖一个线程的存在来执行,但却与允许或禁止线程无关。它只不
过是另一个不同的方法而已。
亦可看出线程并不是按它们创建时的顺序运行的。事实上,CPU 处理一个现有线程集的顺序是不确定的——
除非我们亲自介入,并用Thread 的setPriority()方法调整它们的优先级。
main()创建 Thread 对象时,它并未捕获任何一个对象的句柄。普通对象对于垃圾收集来说是一种 “公平竞
赛”,但线程却并非如此。每个线程都会“注册”自己,所以某处实际存在着对它的一个引用。这样一来,
垃圾收集器便只好对它“瞠目以对”了。
14。1。2 针对用户界面的多线程
现在,我们也许能用一个线程解决在 Counter1。java 中出现的问题。采用的一个技巧便是在一个线程的 run()
方法中放置“子任务”——亦即位于go() 内的循环。一旦用户按下Start 按钮,线程就会启动,但马上结束
线程的创建。这样一来,尽管线程仍在运行,但程序的主要工作却能得以继续(等候并响应用户界面的事
件)。下面是具体的代码:
//: Counter2。java
// A responsive user interface with threads
import java。awt。*;
import java。awt。event。*;
import java。applet。*;
class SeparateSubTask extends Thread {
private int count = 0;
private Counter2 c2;
private boolean runFlag = true;
public SeparateSubTask(Counter2 c2) {
490
…………………………………………………………Page 492……………………………………………………………
this。c2 = c2;
start();
}
public void invertFlag() { runFlag = !runFlag;}
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e){}
if(runFlag)
c2。t。setText(Integer。toString(count++));
}
}
}
public class Counter2 extends Applet {
TextField t = new TextField(10);
private SeparateSubTask sp = null;
private Button
onOff = new Button(〃Toggle〃);
start = new Button(〃Start〃);
public void init() {
add(t);
start。addActionListener(new StartL());
add(start);
onOff。addActionListener(new OnOffL());
add(onOff);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp == null)
sp = new SeparateSubTask(Counter2。this);
}
}
class OnOffL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp != null)
sp。invertFlag();
}
}
public static void main(String'' args) {
Counter2 applet = new Counter2();
Frame aFrame = new Frame(〃Counter2〃);
aFrame。addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System。exit(0);
}
});
aFrame。add(applet; BorderLayout。CENTER);
aFrame。setSize(300;200);
applet。init();
491
…………………………………………………………Page 493……………………………………………………………
applet。start();
aFrame。setVisible(true);
}
} ///:~
现在,Counter2 变成了一个相当直接的程序,它的唯一任务就是设置并管理用户界面。但假若用户现在按下
Start 按钮,却不会真正调用一个方法。此时不是创建类的一个线程,而是创建 SeparateSubTask,然后继续
Counter2 事件循环。注意此时会保存 SeparateSubTask 的句柄,以便我们按下onOff 按钮的时候,能正常地
切换位于SeparateSubTask 内部的runFlag (运行标志)。随后那个线程便可启动(当它看到标志的时
候),然后将自己中止(亦可将SeparateSubTask 设为一个内部类来达到这一目的)。
SeparateSubTask 类是对 Thread 的一个简单扩展,它带有一个构建器(其中保存了 Counter2 句柄,然后通
过调用 start()来运行线程)以及一个 run()——本质上包含了Counter1。java 的go()内的代码。由于
SeparateSubTask 知道自己容纳了指向一个 Counter2 的句柄,所以能够在需要的时候介入,并访问 Counter2
的TestField (文本字段)。
按下onOff 按钮,几乎立即能得到正确的响应。当然,这个响应其实并不是“立即”发生的,它毕竟和那种
由“中断”驱动的系统不同。只有线程拥有CPU 的执行时间,并注意到标记已发生改变,计数器才会停止。
1。 用内部类改善代码
下面说说题外话,请大家注意一下 SeparateSubTask 和 Counter2 类之间发生的结合行为。SeparateSubTask
同Counter2 “亲密”地结合到了一起——它必须持有指向自己“父”Counter2 对象的一个句柄,以便自己能
回调和操纵它。但两个类并不是真的合并为单独一个类(尽管在下一节中,我们会讲到Java 确实提供了合并
它们的方法),因为它们各自做的是不同的事情,而且是在不同的时间创建的。但不管怎样,它们依然紧密
地结合到一起(更准确地说,应该叫“联合”),所以使程序代码多少显得有些笨拙。在这种情况下,一个
内部类可以显著改善代码的“可读性”和执行效率:
//: Counter2i。java
// Counter2 using an inner class for the thread
import java。awt。*;
import java。awt。event。*;
import java。applet。*;
public class Counter2i extends Applet {
private class SeparateSubTask extends Thread {
int count = 0;
boolean runFlag = true;
SeparateSubTask() { start(); }
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e){}
if(runFlag)
t。setText(Integer。toString(count++));
}
}
}
private SeparateSubTask sp = null;
private TextField t = new TextField(10);
private Button
onOff = new Button(〃Toggle〃);
start = new Button(〃Start〃);
public void init() {
492
…………………………………………………………Page 494……………………………………………………………
add(t);
start。addActionListener(new StartL());
add(start);
onOff。addActionListener(new OnOffL());
add(onOff);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp == null)
sp = new SeparateSubTask();
}
}
class OnOffL implements ActionListener {
public vo id actionPerformed(ActionEvent e) {
if(sp != null)
sp。runFlag = !sp。runFlag; // invertFlag();
}
}
public static void main(String'' args) {
Counter2i applet = new Counter2i();
Frame aFrame = new Frame(〃Counter2i〃);
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);
}
} ///:~
这个SeparateSubTask 名字不会与前例中的 SeparateSubTask 冲突——即使它们都在相同的目录里——因为
它已作为一个内部类隐藏起来。大家亦可看到内部类被设为private (私有)属性,这意味着它的字段和方
法都可获得默认的访问权限(run()除外,它必须设为 public,因为它在基础类中是公开的)。除Counter2i
之外,其他任何方面都不可访问private 内部类。而且由于两个类紧密结合在一起,所以很容易放宽它们之
间的访问限制。在SeparateSubTask 中,我们可看到invertFlag()方法已被删去,因为 Counter2i 现在可以
直接访问runFlag。
此外,注意 SeparateSubTask 的构建器已得到了简化——它现在唯一的用外就是启动线程。Counter2i 对象
的句柄仍象以前那样得以捕获,但不再是通过人工传递和引用外部对象来达到这一目的,此时的内部类机制
可以自动照料它。在run()中,可看到对t 的访问是�