按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
对notifyListeners()调用的中途,也可能发出对addActionListener()和 removeActionListener() 的调
用。这显然会造成问题,因为它否定了Vector actionListeners 。为缓解这个问题,我们在一个
synchronized从句中“克隆”了 Vector,并对克隆进行了否定。这样便可在不影响notifyListeners()的前
提下,对Vector 进行操纵。
509
…………………………………………………………Page 511……………………………………………………………
paint()方法也没有设为“同步”。与单纯地添加自己的方法相比,决定是否对过载的方法进行同步要困难得
多。在这个例子中,无论paint()是否“同步”,它似乎都能正常地工作。但必须考虑的问题包括:
(1) 方法会在对象内部修改“关键”变量的状态吗?为判断一个变量是否“关键”,必须知道它是否会被程
序中的其他线程读取或设置(就目前的情况看,读取或设置几乎肯定是通过“同步”方法进行的,所以可以
只对它们进行检查)。对paint()的情况来说,不会发生任何修改。
(2) 方法要以这些“关键”变量的状态为基础吗?如果一个“同步”方法修改了一个变量,而我们的方法要
用到这个变量,那么一般都愿意把自己的方法也设为“同步”。基于这一前提,大家可观察到cSize 由“同
步”方法进行了修改,所以paint()应当是“同步”的。但在这里,我们可以问:“假如 cSize 在paint()执
行期间发生了变化,会发生的最糟糕的事情是什么呢?”如果发现情况不算太坏,而且仅仅是暂时的效果,
那么最好保持paint()的“不同步”状态,以避免同步方法调用带来的额外开销。
(3) 要留意的第三条线索是 paint()基础类版本是否“同步”,在这里它不是同步的。这并不是一个非常严
格的参数,仅仅是一条“线索”。比如在目前的情况下,通过同步方法(好cSize)改变的一个字段已合成
到paint()公式里,而且可能已改变了情况。但请注意,synchronized不能继承——也就是说,假如一个方
法在基础类中是“同步”的,那么在衍生类过载版本中,它不会自动进入“同步”状态。
TestBangBean2 中的测试代码已在前一章的基础上进行了修改,已在其中加入了额外的“听众”,从而演示
了BangBean2 的多造型能力。
14。3 堵塞
一个线程可以有四种状态:
(1) 新(New):线程对象已经创建,但尚未启动,所以不可运行。
(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
…………………………………………………………Pag