Java编程思想第4版[中文版](PDF格式)-第132部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
(2) 可运行(Runnable ):意味着一旦时间分片机制有空闲的CPU 周期提供给一个线程,那个线程便可立即
开始运行。因此,线程可能在、也可能不在运行当中,但一旦条件许可,没有什么能阻止它的运行——它既
没有“死”掉,也未被“堵塞”。
(3) 死(Dead ):从自己的run()方法中返回后,一个线程便已“死”掉。亦可调用 stop()令其死掉,但会
产生一个违例——属于Error 的一个子类(也就是说,我们通常不捕获它)。记住一个违例的“掷”出应当
是一个特殊事件,而不是正常程序运行的一部分。所以不建议你使用 stop() (在Java 1。2 则是坚决反
对)。另外还有一个destroy()方法(它永远不会实现),应该尽可能地避免调用它,因为它非常武断,根
本不会解除对象的锁定。
(4) 堵塞(Blocked):线程可以运行,但有某种东西阻碍了它。若线程处于堵塞状态,调度机制可以简单地
跳过它,不给它分配任何CPU 时间。除非线程再次进入“可运行”状态,否则不会采取任何操作。
14。3。1 为何会堵塞
堵塞状态是前述四种状态中最有趣的,值得我们作进一步的探讨。线程被堵塞可能是由下述五方面的原因造
成的:
(1) 调用 sleep(毫秒数),使线程进入“睡眠”状态。在规定的时间内,这个线程是不会运行的。
(2) 用 suspend()暂停了线程的执行。除非线程收到 resume()消息,否则不会返回“可运行”状态。
(3) 用wait()暂停了线程的执行。除非线程收到 nofify()或者notifyAll()消息,否则不会变成“可运行”
(是的,这看起来同原因2 非常相象,但有一个明显的区别是我们马上要揭示的)。
(4) 线程正在等候一些 IO (输入输出)操作完成。
(5) 线程试图调用另一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。
亦可调用yield() (Thread 类的一个方法)自动放弃CPU,以便其他线程能够运行。然而,假如调度机制觉
得我们的线程已拥有足够的时间,并跳转到另一个线程,就会发生同样的事情。也就是说,没有什么能防止
调度机制重新启动我们的线程。线程被堵塞后,便有一些原因造成它不能继续运行。
下面这个例子展示了进入堵塞状态的全部五种途径。它们全都存在于名为 Blocking。java 的一个文件中,但
在这儿采用散落的片断进行解释(大家可注意到片断前后的“Continued”以及“Continuing”标志。利用第
17章介绍的工具,可将这些片断连结到一起)。首先让我们看看基本的框架:
//: Blocking。java
// Demonstrates the various ways a thread
// can be blocked。
510
…………………………………………………………Page 512……………………………………………………………
import java。awt。*;
import java。awt。event。*;
import java。applet。*;
import java。io。*;
//////////// The basic framework ///////////
class Blockable extends Thread {
private Peeker peeker;
protected TextField state = new TextField(40);
protected int i;
public Blockable(Container c) {
c。add(state);
peeker = new Peeker(this; c);
}
public synchronized int read() { return i; }
protected synchronized void update() {
state。setText(getClass()。getName()
+ 〃 state: i = 〃 + i);
}
public void stopPeeker() {
// peeker。stop(); Deprecated in Java 1。2
peeker。terminate(); // The preferred approach
}
}
class Peeker extends Thread {
private Blockable b;
private int session;
private TextField status = new TextFie ld(40);
private boolean stop = false;
public Peeker(Blockable b; Container c) {
c。add(status);
this。b = b;
start();
}
public void terminate() { stop = true; }
public void run() {
while (!stop) {
status。setText(b。getClass()。getName()
+ 〃 Peeker 〃 + (++session)
+ 〃; value = 〃 + b。read());
try {
sleep(100);
} catch (InterruptedException e){}
}
}
} ///:Continued
Blockable 类打算成为本例所有类的一个基础类。一个Blockable 对象包含了一个名为 state 的TextField
(文本字段),用于显示出对象有关的信息。用于显示这些信息的方法叫作update() 。我们发现它用
getClass。getName()来产生类名,而不是仅仅把它打印出来;这是由于 update(0 不知道自己为其调用的那个
类的准确名字,因为那个类是从Blockable 衍生出来的。
511
…………………………………………………………Page 513……………………………………………………………
在Blockable 中,变动指示符是一个 int i;衍生类的 run()方法会为其增值。
针对每个Bloackable 对象,都会启动Peeker 类的一个线程。Peeker 的任务是调用read()方法,检查与自己
关联的Blockable 对象,看看 i 是否发生了变化,最后用它的 status文本字段报告检查结果。注意 read()
和update() 都是同步的,要求对象的锁定能自由解除,这一点非常重要。
1。 睡眠
这个程序的第一项测试是用 sleep()作出的:
///:Continuing
///////////// Blocking via sleep() ///////////
class Sleeper1 extends Blockable {
public Sleeper1(Container c) { super(c); }
public synchronized void run() {
while(true) {
i++;
update();
try {
sleep(1000);
} catch (InterruptedException e){}
}
}
}
class Sleeper2 extends Blockable {
public Sleeper2(Container c) { super(c); }
public void run() {
while(true) {
change();
try {
sleep(1000);
} catch (InterruptedException e){}
}
}
public synchronized void change() {
i++;
update();
}
} ///:Continued
在Sleeper1 中,整个run()方法都是同步的。我们可看到与这个对象关联在一起的Peeker 可以正常运行,
直到我们启动线程为止,随后 Peeker 便会完全停止。这正是“堵塞”的一种形式:因为 Sleeper1。run()是
同步的,而且一旦线程启动,它就肯定在run()内部,方法永远不会放弃对象锁定,造成Peeker 线程的堵
塞。
Sleeper2 通过设置不同步的运行,提供了一种解决方案。只有change()方法才是同步的,所以尽管run()位
于 sleep()内部,Peeker 仍然能访问自己需要的同步方法——read()。在这里,我们可看到在启动了
Sleeper2 线程以后,Peeker 会持续运行下去。
2。 暂停和恢复
这个例子接下来的一部分引入了“挂起”或者“暂停”(Suspend)的概述。Thread 类提供了一个名为
suspend()的方法,可临时中止线程;以及一个名为resume() 的方法,用于从暂停处开始恢复线程的执行。
显然,我们可以推断出 resume()是由暂停线程外部的某个线程调用的。在这种情况下,需要用到一个名为
Resumer (恢复器)的独立类。演示暂停/恢复过程的每个类都有一个相关的恢复器。如下所示:
512
…………………………………………………………Page 514……………………………………………………………
///:Continuing
/////////// Blocking via suspend() ///////////
class SuspendResume extends Blockable {
public SuspendResume(Container c) {
super(c);
new Resumer(this);
}
}
class SuspendResume1 extends SuspendResume {
public SuspendResume1(Container c) { super(c);}
public synchronized void run() {
while(true) {
i++;
update();
suspend(); // Deprecated in Java 1。2
}
}
}
class SuspendResume2 extends SuspendResume {
public SuspendResume2(Container c) { super(c);}
public void run() {
while(true) {
change();
suspend(); // Deprecated in Java 1。2
}
}
public synchronized void change() {
i++;
update();
}
}
class Resumer extends Thread {
private SuspendResume sr;
public Resumer(SuspendResume sr) {
this。sr = sr;
start();
}
public void run() {
while(true) {
try {
sleep(1000);
} catch (InterruptedException e){}
sr。resume(); // Deprecated in Java 1。2
}
}
} ///:Continued
SuspendResume1 也提供了一个同步的run()方法。同样地,当我们启动这个线程以后,就会发现与它关联的
513
…………………………………………………………Page 515……………………………………………………………
Peeker 进入“堵塞”状态,等候对象锁被释放,但那永远不会发生。和往常一样,这个问题在
SuspendResume2 里得到了解决,它并不同步整个run()方法,而是采用了一个单独的同步 change()方法。
对于Java 1。2,大家应注意 suspend()和resume() 已获得强烈反对,因为 suspend()包含了对象锁,所以极
易出现“死锁”现象。换言之,很容易就会看到许多被锁住的对象在傻乎乎地等待对方。这会造成整个应用
程序的“凝固”。尽管在一些老程序中还能看到它们的踪迹,但在你写自己的程序时,无论如何都应避免。
本章稍后就会讲述正确的方案是什么。
3。 等待和通知
通过前两个例子的实践,我们知道无论 sleep()还是 suspend()都不会在自己被调用的时候解除锁定。需要用
到对象锁时,请务必注意这个问题。在另一方面,wait()方法在被调用时却会解除锁定,这意味着可在执行
wait()期间调用线程对象中的其他同步方法。但在接着的两个类中,我们看到 run()方法都是“同步”的。
在wait()期间,Peeker 仍然拥有对同步方法的完全访问权限。这是由于wait()在挂起内部调用的方法时,
会解除对象的锁定。
我们也可以看到wait()的两种形式。第一种形式采用一个以毫秒为单位的参数,它具有与sleep()中相同的
含义:暂停这一段规定时间。区别在于在wait()中,对象锁已被解除,而且能够自由地退出wait(),因为一
个notify() 可强行使时间流逝。
第二种形式不采用任何参数,这意味着wait()会持续执行,直到 notify()介入为止。而且在一段时间以后,
不会自行中止。
wait()和 notify() 比较特别的一个地方是这两个方法都属于基础类 Object 的一部分,不象sleep(),
suspend()以及resume()那样属于Thread 的一部分。尽管这表面看有点儿奇怪——居然让专门进行线程处理
的东西成为通用基础类的一部分——但仔细想想又会释然,因为它们操纵的对象锁也属于每个对象的一部
分。因此,我们可将一个wait()置入任何同步方法内部,无论在那个类里是否准备进行涉及线程的处理。事
实上,我们能调用wait()的唯一地方是在一个同步的方法或代码块内部。若在一个不同步的方法内调用
wait()或者 notify(),尽管程序仍然会编译,但在运行它的时候,就会得到一个
IllegalMonitorStateException (非法监视器状态违例),而且会出现多少有点莫名其妙的一条消息:
“current thread not owner”(当前线程不是所有人”。注意 sleep(),suspend()以及resume()都能在不
同步的方法内调用,因为它们不需要对锁定进行操作。
只能为自己的锁定调用 wait()和 notify() 。同样地,仍然可以编译那些试图使用错误锁定的代码,但和往常
一样会产生同样的 I