- 写在前面的话
- 引言
- 第 1 章 对象入门
- 第 2 章 一切都是对象
- 第 3 章 控制程序流程
- 第 4 章 初始化和清除
- 第 5 章 隐藏实施过程
- 第 6 章 类再生
- 第 7 章 多形性
- 第 8 章 对象的容纳
- 第 9 章 违例差错控制
- 第 10 章 Java IO 系统
- 第 11 章 运行期类型鉴定
- 第 12 章 传递和返回对象
- 第 十三 章 创建窗口和程序片
- 第 14 章 多线程
- 第 15 章 网络编程
- 第 16 章 设计范式
- 第 17 章 项目
- 附录 A 使用非 JAVA 代码
- 附录 B 对比 C++和 Java
- 附录 C Java 编程规则
- 附录 D 性能
- 附录 E 关于垃圾收集的一些话
- 附录 F 推荐读物
14.1.4 制作多个线程
现在考虑一下创建多个不同的线程的问题。我们不可用前面的例子来做到这一点,所以必须倒退回去,利用从 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() 会深深地陷入一个无限循环——很难在多任务处理期间中止退出。因此,程序对用户操作的反应灵敏度会大幅度降低。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论