按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
return text;
}
public synchronized void
setBangText(String newText) {
text = newText;
}
public synchronized int getFontSize() {
return fontSize;
}
public synchronized void
setFontSize(int newSize) {
507
…………………………………………………………Page 509……………………………………………………………
fontSize = newSize;
}
public synchronized Color getTextColor() {
return tColor;
}
public synchronized void
setTextColor(Color newColor) {
tColor = newColor;
}
public void paint(Graphics g) {
g。setColor(Color。black);
g。drawOval(xm cSize/2; ym cSize/2;
cSize; cSize);
}
// This is a multicast listener; which is
// more typically used than the unicast
// approach taken in BangBean。java:
public synchronized void addActionListener (
ActionListener l) {
actionListeners。addElement(l);
}
public synchronized void removeActionListener(
ActionListener l) {
actionListeners。removeElement(l);
}
// Notice this isn't synchronized:
public void notifyListeners() {
ActionEvent a =
new ActionEvent(BangBean2。this;
ActionEvent。ACTION_PERFORMED; null);
Vector lv = null;
// Make a copy of the vector in case someone
// adds a listener while we're
// calling listeners:
synchronized(this) {
lv = (Vector)actionListeners。clone();
}
// Call all the listener methods:
for(int i = 0; i 《 lv。size(); i++) {
ActionListener al =
(ActionListener)lv。elementAt(i);
al。actionPerformed(a);
}
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
Graphics g = getGraphics();
g。setColor(tColor);
g。setFont(
new Font(
〃TimesRoman〃; Font。BOLD; fontSize));
int width =
508
…………………………………………………………Page 510……………………………………………………………
g。getFontMetrics()。stringWidth(text);
g。drawString(text;
(getSize()。width width) /2;
getSize()。height/2);
g。dispose();
notifyListeners();
}
}
class MM extends MouseMotionAdapter {
public void mouseMoved(MouseEvent e) {
xm = e。getX();
ym = e。getY();
repaint();
}
}
// Testing the BangBean2:
public static void main(String'' args) {
BangBean2 bb = new BangBean2();
bb。addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
System。out。println(〃ActionEvent〃 + e);
}
});
bb。addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
System。out。println(〃BangBean2 action〃);
}
});
bb。addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
System。out。println(〃More action〃);
}
});
Frame aFrame = new Frame(〃BangBean2 Test〃);
aFrame。addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
System。exit(0);
}
});
aFrame。add(bb; BorderLayout。CENTER);
aFrame。setSize(300;300);
aFrame。setVisible(true);
}
} ///:~
很容易就可以为方法添加synchronized。但注意在addActionListener()和 removeActionListener()中,现
在添加了ActionListener,并从一个 Vector 中移去,所以能够根据自己愿望使用任意多个。
我们注意到,notifyListeners()方法并未设为“同步”。可从多个线程中发出对这个方法的调用。另外,在
对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。