14.1 反应灵敏的用户界面.md 28.4 KB
Newer Older
1 2
# 14.1 反应灵敏的用户界面

3
作为我们的起点,请思考一个需要执行某些CPU密集型计算的程序。由于CPU“全心全意”为那些计算服务,所以对用户的输入十分迟钝,几乎没有什么反应。在这里,我们用一个合成的applet/application(程序片/应用程序)来简单显示出一个计数器的结果:
4 5

```
6 7 8 9 10 11 12 13 14
//: Counter1.java
// A non-responsive user interface
package c14;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Counter1 extends Applet {
  private int count = 0;
W
14.1~2  
wizardforcel 已提交
15
  private Button
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
    onOff = new Button("Toggle"),
    start = new Button("Start");
  private TextField t = new TextField(10);
  private boolean runFlag = true;
  public void init() {
    add(t);
    start.addActionListener(new StartL());
    add(start);
    onOff.addActionListener(new OnOffL());
    add(onOff);
  }
  public void go() {
    while (true) {
      try {
        Thread.currentThread().sleep(100);
      } catch (InterruptedException e){}
W
14.1~2  
wizardforcel 已提交
32
      if(runFlag)
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
        t.setText(Integer.toString(count++));
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      go();
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  public static void main(String[] args) {
    Counter1 applet = new Counter1();
    Frame aFrame = new Frame("Counter1");
    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);
  }
} ///:~
62
```
63

W
wizardforcel 已提交
64
在这个程序中,AWT和程序片代码都应是大家熟悉的,第13章对此已有很详细的交待。`go()`方法正是程序全心全意服务的对待:将当前的`count`(计数)值置入`TextField`(文本字段)`t`,然后使`count`自增。
65

W
14.1~2  
wizardforcel 已提交
66
`go()`内的部分无限循环是调用`sleep()``sleep()`必须同一个`Thread`(线程)对象关联到一起,而且似乎每个应用程序都有部分线程同它关联(事实上,Java本身就是建立在线程基础上的,肯定有一些线程会伴随我们写的应用一起运行)。所以无论我们是否明确使用了线程,都可利用`Thread.currentThread()`产生由程序使用的当前线程,然后为那个线程调用`sleep()`。注意,`Thread.currentThread()``Thread`类的一个静态方法。
67

W
14.1~2  
wizardforcel 已提交
68
注意`sleep()`可能“抛”出一个`InterruptException`(中断异常)——尽管产生这样的异常被认为是中止线程的一种“恶意”手段,而且应该尽可能地杜绝这一做法。再次提醒大家,异常是为异常情况而产生的,而不是为了正常的控制流。在这里包含了对一个“睡眠”线程的中断,以支持未来的一种语言特性。
69

W
14.1~2  
wizardforcel 已提交
70
一旦按下`start`按钮,就会调用`go()`。研究一下`go()`,你可能会很自然地(就象我一样)认为它该支持多线程,因为它会进入“睡眠”状态。也就是说,尽管方法本身“睡着”了,CPU仍然应该忙于监视其他按钮“按下”事件。但有一个问题,那就是`go()`是永远不会返回的,因为它被设计成一个无限循环。这意味着`actionPerformed()`根本不会返回。由于在第一个按键以后便陷入`actionPerformed()`中,所以程序不能再对其他任何事件进行控制(如果想出来,必须以某种方式“杀死”进程——最简便的方式就是在控制台窗口按`Ctrl+C`键)。
71

W
14.1~2  
wizardforcel 已提交
72
这里最基本的问题是`go()`需要继续执行自己的操作,而与此同时,它也需要返回,以便`actionPerformed()`能够完成,而且用户界面也能继续响应用户的操作。但对象`go()`这样的传统方法来说,它却不能在继续的同时将控制权返回给程序的其他部分。这听起来似乎是一件不可能做到的事情,就象CPU必须同时位于两个地方一样,但线程可以解决一切。“线程模型”(以及Java中的编程支持)是一种程序编写规范,可在单独一个程序里实现几个操作的同时进行。根据这一机制,CPU可为每个线程都分配自己的一部分时间。每个线程都“感觉”自己好象拥有整个CPU,但CPU的计算时间实际却是在所有线程间分摊的。
73

74 75
线程机制多少降低了一些计算效率,但无论程序的设计,资源的均衡,还是用户操作的方便性,都从中获得了巨大的利益。综合考虑,这一机制是非常有价值的。当然,如果本来就安装了多块CPU,那么操作系统能够自行决定为不同的CPU分配哪些线程,程序的总体运行速度也会变得更快(所有这些都要求操作系统以及应用程序的支持)。多线程和多任务是充分发挥多处理机系统能力的一种最有效的方式。

W
14.1~2  
wizardforcel 已提交
76
## 14.1.1 从线程继承
77

W
14.1~2  
wizardforcel 已提交
78
为创建一个线程,最简单的方法就是从`Thread`类继承。这个类包含了创建和运行线程所需的一切东西。`Thread`最重要的方法是`run()`。但为了使用`run()`,必须对其进行重载或者覆盖,使其能充分按自己的吩咐行事。因此,`run()`属于那些会与程序中的其他线程“并发”或“同时”执行的代码。
79

W
14.1~2  
wizardforcel 已提交
80
下面这个例子可创建任意数量的线程,并通过为每个线程分配一个独一无二的编号(由一个静态变量产生),从而对不同的线程进行跟踪。`Thread``run()`方法在这里得到了覆盖,每通过一次循环,计数就减1——计数为0时则完成循环(此时一旦返回run(),线程就中止运行)。
81 82

```
83 84 85 86 87 88 89 90 91 92 93 94 95
//: SimpleThread.java
// Very simple Threading example

public class SimpleThread extends Thread {
  private int countDown = 5;
  private int threadNumber;
  private static int threadCount = 0;
  public SimpleThread() {
    threadNumber = ++threadCount;
    System.out.println("Making " + threadNumber);
  }
  public void run() {
    while(true) {
W
14.1~2  
wizardforcel 已提交
96
      System.out.println("Thread " +
97 98 99 100 101 102 103 104 105 106
        threadNumber + "(" + countDown + ")");
      if(--countDown == 0) return;
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++)
      new SimpleThread().start();
    System.out.println("All Threads Started");
  }
} ///:~
107
```
108

W
14.1~2  
wizardforcel 已提交
109
`run()`方法几乎肯定含有某种形式的循环——它们会一直持续到线程不再需要为止。因此,我们必须规定特定的条件,以便中断并退出这个循环(或者在上述的例子中,简单地从`run()`返回即可)。`run()`通常采用一种无限循环的形式。也就是说,通过阻止外部发出对线程的`stop()`或者`destroy()`调用,它会永远运行下去(直到程序完成)。
110

W
14.1~2  
wizardforcel 已提交
111
`main()`中,可看到创建并运行了大量线程。`Thread`包含了一个特殊的方法,叫作`start()`,它的作用是对线程进行特殊的初始化,然后调用`run()`。所以整个步骤包括:调用构造器来构建对象,然后用`start()`配置线程,再调用`run()`。如果不调用`start()`——如果适当的话,可在构造器那样做——线程便永远不会启动。
112

113
下面是该程序某一次运行的输出(注意每次运行都会不同):
114 115

```
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
Making 1
Making 2
Making 3
Making 4
Making 5
Thread 1(5)
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)
147
```
148

W
14.1~2  
wizardforcel 已提交
149
可注意到这个例子中到处都调用了`sleep()`,然而输出结果指出每个线程都获得了属于自己的那一部分CPU执行时间。从中可以看出,尽管`sleep()`依赖一个线程的存在来执行,但却与允许或禁止线程无关。它只不过是另一个不同的方法而已。
150

W
14.1~2  
wizardforcel 已提交
151
亦可看出线程并不是按它们创建时的顺序运行的。事实上,CPU处理一个现有线程集的顺序是不确定的——除非我们亲自介入,并用`Thread``setPriority()`方法调整它们的优先级。
152

W
14.1~2  
wizardforcel 已提交
153
`main()`创建`Thread`对象时,它并未捕获任何一个对象的引用。普通对象对于垃圾收集来说是一种“公平竞赛”,但线程却并非如此。每个线程都会“注册”自己,所以某处实际存在着对它的一个引用。这样一来,垃圾收集器便只好对它“瞠目以对”了。
154

W
14.1~2  
wizardforcel 已提交
155
## 14.1.2 针对用户界面的多线程
156

W
14.1~2  
wizardforcel 已提交
157
现在,我们也许能用一个线程解决在`Counter1.java`中出现的问题。采用的一个技巧便是在一个线程的`run()`方法中放置“子任务”——亦即位于`go()`内的循环。一旦用户按下`Start`按钮,线程就会启动,但马上结束线程的创建。这样一来,尽管线程仍在运行,但程序的主要工作却能得以继续(等候并响应用户界面的事件)。下面是具体的代码:
158 159

```
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
//: 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) {
    this.c2 = c2;
    start();
  }
  public void invertFlag() { runFlag = !runFlag;}
  public void run() {
    while (true) {
     try {
      sleep(100);
     } catch (InterruptedException e){}
W
14.1~2  
wizardforcel 已提交
180
     if(runFlag)
181 182 183
       c2.t.setText(Integer.toString(count++));
    }
  }
W
14.1~2  
wizardforcel 已提交
184
}
185 186 187 188

public class Counter2 extends Applet {
  TextField t = new TextField(10);
  private SeparateSubTask sp = null;
W
14.1~2  
wizardforcel 已提交
189
  private Button
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    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();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~
227
```
228

W
14.1~2  
wizardforcel 已提交
229
现在,`Counter2`变成了一个相当直接的程序,它的唯一任务就是设置并管理用户界面。但假若用户现在按下`Start`按钮,却不会真正调用一个方法。此时不是创建类的一个线程,而是创建`SeparateSubTask`,然后继续`Counter2`事件循环。注意此时会保存`SeparateSubTask`的引用,以便我们按下`onOff`按钮的时候,能正常地切换位于`SeparateSubTask`内部的`runFlag`(运行标志)。随后那个线程便可启动(当它看到标志的时候),然后将自己中止(亦可将`SeparateSubTask`设为一个内部类来达到这一目的)。
230

W
14.1~2  
wizardforcel 已提交
231
`SeparateSubTask`类是对`Thread`的一个简单扩展,它带有一个构造器(其中保存了`Counter2`引用,然后通过调用`start()`来运行线程)以及一个`run()`——本质上包含了`Counter1.java``go()`内的代码。由于`SeparateSubTask`知道自己容纳了指向一个`Counter2`的引用,所以能够在需要的时候介入,并访问`Counter2``TestField`(文本字段)。
232

W
14.1~2  
wizardforcel 已提交
233
按下`onOff`按钮,几乎立即能得到正确的响应。当然,这个响应其实并不是“立即”发生的,它毕竟和那种由“中断”驱动的系统不同。只有线程拥有CPU的执行时间,并注意到标记已发生改变,计数器才会停止。
234 235

1. 用内部类改善代码
236

W
14.1~2  
wizardforcel 已提交
237
下面说说题外话,请大家注意一下`SeparateSubTask``Counter2`类之间发生的结合行为。`SeparateSubTask``Counter2`“亲密”地结合到了一起——它必须持有指向自己“父”`Counter2`对象的一个引用,以便自己能回调和操纵它。但两个类并不是真的合并为单独一个类(尽管在下一节中,我们会讲到Java确实提供了合并它们的方法),因为它们各自做的是不同的事情,而且是在不同的时间创建的。但不管怎样,它们依然紧密地结合到一起(更准确地说,应该叫“联合”),所以使程序代码多少显得有些笨拙。在这种情况下,一个内部类可以显著改善代码的“可读性”和执行效率:
238 239

```
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
//: 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){}
W
14.1~2  
wizardforcel 已提交
256
       if(runFlag)
257 258 259
         t.setText(Integer.toString(count++));
      }
    }
W
14.1~2  
wizardforcel 已提交
260
  }
261 262
  private SeparateSubTask sp = null;
  private TextField t = new TextField(10);
W
14.1~2  
wizardforcel 已提交
263
  private Button
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
    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();
    }
  }
  class OnOffL implements ActionListener {
    public void 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);
  }
} ///:~
301
```
302

W
14.1~2  
wizardforcel 已提交
303
这个`SeparateSubTask`名字不会与前例中的`SeparateSubTask`冲突——即使它们都在相同的目录里——因为它已作为一个内部类隐藏起来。大家亦可看到内部类被设为`private`(私有)属性,这意味着它的字段和方法都可获得默认的访问权限(`run()`除外,它必须设为`public`,因为它在基类中是公开的)。除`Counter2i`之外,其他任何方面都不可访问`private`内部类。而且由于两个类紧密结合在一起,所以很容易放宽它们之间的访问限制。在`SeparateSubTask`中,我们可看到`invertFlag()`方法已被删去,因为`Counter2i`现在可以直接访问`runFlag`
304

W
14.1~2  
wizardforcel 已提交
305
此外,注意`SeparateSubTask`的构造器已得到了简化——它现在唯一的用外就是启动线程。`Counter2i`对象的引用仍象以前那样得以捕获,但不再是通过人工传递和引用外部对象来达到这一目的,此时的内部类机制可以自动照料它。在`run()`中,可看到对`t`的访问是直接进行的,似乎它是`SeparateSubTask`的一个字段。父类中的t字段现在可以变成`private`,因为`SeparateSubTask`能在未获任何特殊许可的前提下自由地访问它——而且无论如何都该尽可能地把字段变成“私有”属性,以防来自类外的某种力量不慎地改变它们。
306

307 308
无论在什么时候,只要注意到类相互之间结合得比较紧密,就可考虑利用内部类来改善代码的编写与维护。

W
14.1~2  
wizardforcel 已提交
309
## 14.1.3 用主类合并线程
310

W
14.1~2  
wizardforcel 已提交
311
在上面的例子中,我们看到线程类(`Thread`)与程序的主类(`Main`)是分隔开的。这样做非常合理,而且易于理解。然而,还有另一种方式也是经常要用到的。尽管它不十分明确,但一般都要更简洁一些(这也解释了它为什么十分流行)。通过将主程序类变成一个线程,这种形式可将主程序类与线程类合并到一起。由于对一个GUI程序来说,主程序类必须从`Frame``Applet`继承,所以必须用一个接口加入额外的功能。这个接口叫作`Runnable`,其中包含了与`Thread`一致的基本方法。事实上,`Thread`也实现了`Runnable`,它只指出有一个`run()`方法。
312

W
14.1~2  
wizardforcel 已提交
313
对合并后的程序/线程来说,它的用法不是十分明确。当我们启动程序时,会创建一个`Runnable`(可运行的)对象,但不会自行启动线程。线程的启动必须明确进行。下面这个程序向我们演示了这一点,它再现了`Counter2`的功能:
314 315

```
316
//: Counter3.java
W
14.1~2  
wizardforcel 已提交
317
// Using the Runnable interface to turn the
318 319 320 321 322
// main class into a thread.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

W
14.1~2  
wizardforcel 已提交
323
public class Counter3
324 325 326 327
    extends Applet implements Runnable {
  private int count = 0;
  private boolean runFlag = true;
  private Thread selfThread = null;
W
14.1~2  
wizardforcel 已提交
328
  private Button
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
    onOff = new Button("Toggle"),
    start = new Button("Start");
  private TextField t = new TextField(10);
  public void init() {
    add(t);
    start.addActionListener(new StartL());
    add(start);
    onOff.addActionListener(new OnOffL());
    add(onOff);
  }
  public void run() {
    while (true) {
      try {
        selfThread.sleep(100);
      } catch (InterruptedException e){}
W
14.1~2  
wizardforcel 已提交
344
      if(runFlag)
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
        t.setText(Integer.toString(count++));
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(selfThread == null) {
        selfThread = new Thread(Counter3.this);
        selfThread.start();
      }
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  public static void main(String[] args) {
    Counter3 applet = new Counter3();
    Frame aFrame = new Frame("Counter3");
    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);
  }
} ///:~
377
```
378

W
14.1~2  
wizardforcel 已提交
379
现在`run()`位于类内,但它在`init()`结束以后仍处在“睡眠”状态。若按下启动按钮,线程便会用多少有些暧昧的表达方式创建(若线程尚不存在):
380 381

```
382
new Thread(Counter3.this);
383 384
```

W
14.1~2  
wizardforcel 已提交
385
若某样东西有一个`Runnable`接口,实际只是意味着它有一个`run()`方法,但不存在与之相关的任何特殊东西——它不具有任何天生的线程处理能力,这与那些从`Thread`继承的类是不同的。所以为了从一个`Runnable`对象产生线程,必须单独创建一个线程,并为其传递`Runnable`对象;可为其使用一个特殊的构造器,并令其采用一个`Runnable`作为自己的参数使用。随后便可为那个线程调用`start()`,如下所示:
386 387

```
388
selfThread.start();
389 390
```

W
14.1~2  
wizardforcel 已提交
391
它的作用是执行常规初始化操作,然后调用`run()`
392

W
14.1~2  
wizardforcel 已提交
393
`Runnable`接口最大的一个优点是所有东西都从属于相同的类。若需访问什么东西,只需简单地访问它即可,不需要涉及一个独立的对象。但为这种便利也是要付出代价的——只可为那个特定的对象运行单独一个线程(尽管可创建那种类型的多个对象,或者在不同的类里创建其他对象)。
394

W
14.1~2  
wizardforcel 已提交
395
注意`Runnable`接口本身并不是造成这一限制的罪魁祸首。它是由于`Runnable`与我们的主类合并造成的,因为每个应用只能主类的一个对象。
396

W
14.1~2  
wizardforcel 已提交
397
## 14.1.4 制作多个线程
398

W
14.1~2  
wizardforcel 已提交
399
现在考虑一下创建多个不同的线程的问题。我们不可用前面的例子来做到这一点,所以必须倒退回去,利用从`Thread`继承的多个独立类来封装`run()`。但这是一种更常规的方案,而且更易理解,所以尽管前例揭示了我们经常都能看到的编码样式,但并不推荐在大多数情况下都那样做,因为它只是稍微复杂一些,而且灵活性稍低一些。
400

W
14.1~2  
wizardforcel 已提交
401
下面这个例子用计数器和切换按钮再现了前面的编码样式。但这一次,一个特定计数器的所有信息(按钮和文本字段)都位于它自己的、从`Thread`继承的对象内。`Ticker`中的所有字段都具有`private`(私有)属性,这意味着`Ticker`的具体实现方案可根据实际情况任意修改,其中包括修改用于获取和显示信息的数据组件的数量及类型。创建好一个`Ticker`对象以后,构造器便请求一个AWT容器(`Container`)的引用——`Ticker`用自己的可视组件填充那个容器。采用这种方式,以后一旦改变了可视组件,使用`Ticker`的代码便不需要另行修改一道。
402 403

```
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
//: 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)
W
14.1~2  
wizardforcel 已提交
449
      size =
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
        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;
W
14.1~2  
wizardforcel 已提交
471
    applet.size =
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
      (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);
  }
} ///:~
488
```
489

W
14.1~2  
wizardforcel 已提交
490
`Ticker`不仅包括了自己的线程处理机制,也提供了控制与显示线程的工具。可按自己的意愿创建任意数量的线程,毋需明确地创建窗口化组件。
491

W
14.1~2  
wizardforcel 已提交
492
`Counter4`中,有一个名为`s``Ticker`对象的数组。为获得最大的灵活性,这个数组的长度是用程序片参数接触Web页而初始化的。下面是网页中长度参数大致的样子,它们嵌于对程序片(applet)的描述内容中:
493 494

```
495 496 497
<applet code=Counter4 width=600 height=600>
<param name=size value="20">
</applet>
498 499
```

W
14.1~2  
wizardforcel 已提交
500
其中,`param``name``value`是所有Web页都适用的关键字。`name`是指程序中对参数的一种引用称谓,`value`可以是任何字符串(并不仅仅是解析成一个数字的东西)。
501

W
14.1~2  
wizardforcel 已提交
502
我们注意到对数组`s`长度的判断是在`init()`内部完成的,它没有作为`s`的内嵌定义的一部分提供。换言之,不可将下述代码作为类定义的一部分使用(应该位于任何方法的外部):
503 504

```
505 506
inst size = Integer.parseInt(getParameter("Size"));
Ticker[] s = new Ticker[size]
507 508
```

W
14.1~2  
wizardforcel 已提交
509
可把它编译出来,但会在运行期得到一个空指针异常。但若将`getParameter()`初始化移入`init()`,则可正常工作。程序片框架会进行必要的启动工作,以便在进入`init()`前收集好一些参数。
510

W
14.1~2  
wizardforcel 已提交
511
此外,上述代码被同时设置成一个程序片和一个应用(程序)。在它是应用程序的情况下,`size`参数可从命令行里提取出来(否则就提供一个默认的值)。
512

W
14.1~2  
wizardforcel 已提交
513
数组的长度建好以后,就可以创建新的`Ticker`对象;作为`Ticker`构造器的一部分,用于每个`Ticker`的按钮和文本字段就会加入程序片。
514

W
14.1~2  
wizardforcel 已提交
515
按下`Start`按钮后,会在整个`Ticker`数组里遍历,并为每个`Ticker`调用`start()`。记住,`start()`会进行必要的线程初始化工作,然后为那个线程调用`run()`
516

W
14.1~2  
wizardforcel 已提交
517
`ToggleL`监视器只是简单地切换`Ticker`中的标记,一旦对应线程以后需要修改这个标记,它会作出相应的反应。
518

519
这个例子的一个好处是它使我们能够方便地创建由单独子任务构成的大型集合,并以监视它们的行为。在这种情况下,我们会发现随着子任务数量的增多,机器显示出来的数字可能会出现更大的分歧,这是由于为线程提供服务的方式造成的。
520

W
14.1~2  
wizardforcel 已提交
521
亦可试着体验一下`sleep(100)``Ticker.run()`中的重要作用。若删除`sleep()`,那么在按下一个切换按钮前,情况仍然会进展良好。按下按钮以后,那个特定的线程就会出现一个失败的`runFlag`,而且`run()`会深深地陷入一个无限循环——很难在多任务处理期间中止退出。因此,程序对用户操作的反应灵敏度会大幅度降低。
522

W
14.1~2  
wizardforcel 已提交
523
## 14.1.5 Daemon线程
524 525

“Daemon”线程的作用是在程序的运行期间于后台提供一种“常规”服务,但它并不属于程序的一个基本部分。因此,一旦所有非
W
14.1~2  
wizardforcel 已提交
526
Daemon线程完成,程序也会中止运行。相反,假若有任何非Daemon线程仍在运行(比如还有一个正在运行`main()`的线程),则程序的运行不会中止。
527

W
14.1~2  
wizardforcel 已提交
528
通过调用`isDaemon()`,可调查一个线程是不是一个Daemon,而且能用`setDaemon()`打开或者关闭一个线程的Daemon状态。如果是一个Daemon线程,那么它创建的任何线程也会自动具备Daemon属性。
529

530
下面这个例子演示了Daemon线程的用法:
531 532

```
533 534 535 536 537 538 539
//: Daemons.java
// Daemonic behavior
import java.io.*;

class Daemon extends Thread {
  private static final int SIZE = 10;
  private Thread[] t = new Thread[SIZE];
W
14.1~2  
wizardforcel 已提交
540
  public Daemon() {
541 542 543 544 545 546 547 548
    setDaemon(true);
    start();
  }
  public void run() {
    for(int i = 0; i < SIZE; i++)
      t[i] = new DaemonSpawn(i);
    for(int i = 0; i < SIZE; i++)
      System.out.println(
W
14.1~2  
wizardforcel 已提交
549
        "t[" + i + "].isDaemon() = "
550
        + t[i].isDaemon());
W
14.1~2  
wizardforcel 已提交
551
    while(true)
552 553 554 555 556 557 558 559 560 561 562
      yield();
  }
}

class DaemonSpawn extends Thread {
  public DaemonSpawn(int i) {
    System.out.println(
      "DaemonSpawn " + i + " started");
    start();
  }
  public void run() {
W
14.1~2  
wizardforcel 已提交
563
    while(true)
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
      yield();
  }
}

public class Daemons {
  public static void main(String[] args) {
    Thread d = new Daemon();
    System.out.println(
      "d.isDaemon() = " + d.isDaemon());
    // Allow the daemon threads to finish
    // their startup processes:
    BufferedReader stdin =
      new BufferedReader(
        new InputStreamReader(System.in));
    System.out.println("Waiting for CR");
    try {
      stdin.readLine();
    } catch(IOException e) {}
  }
} ///:~
584
```
585

W
wizardforcel 已提交
586
Daemon线程可将自己的Daemon标记设置成“真”,然后产生一系列其他线程,而且认为它们也具有Daemon属性。随后,它进入一个无限循环,在其中调用`yield()`,放弃对其他进程的控制。在这个程序早期的一个版本中,无限循环会使`int`计数器自增,但会使整个程序都好象陷入停顿状态。换用`yield()`后,却可使程序充满“活力”,不会使人产生停滞或反应迟钝的感觉。
587

W
14.1~2  
wizardforcel 已提交
588
一旦`main()`完成自己的工作,便没有什么能阻止程序中断运行,因为这里运行的只有Daemon线程。所以能看到启动所有Daemon线程后显示出来的结果,`System.in`也进行了相应的设置,使程序中断前能等待一个回车。如果不进行这样的设置,就只能看到创建Daemon线程的一部分结果(试试将`readLine()`代码换成不同长度的`sleep()`调用,看看会有什么表现)。