堵塞状态是前述四种状态中最有趣的值得我们作进一步的探讨线程被堵塞可能是由下述五方面的原因造成的 () 调用sleep(毫秒数)使线程进入睡眠状态在规定的时间内这个线程是不会运行的 () 用suspend()暂停了线程的执行除非线程收到resume()消息否则不会返回可运行状态 () 用wait()暂停了线程的执行除非线程收到nofify()或者notifyAll()消息否则不会变成可运行(是的这看起来同原因非常相象但有一个明显的区别是我们马上要揭示的) () 线程正在等候一些IO(输入输出)操作完成 () 线程试图调用另一个对象的同步方法但那个对象处于锁定状态暂时无法使用 亦可调用yield()(Thread类的一个方法)自动放弃CPU以便其他线程能够运行然而假如调度机制觉得我们的线程已拥有足够的时间并跳转到另一个线程就会发生同样的事情也就是说没有什么能防止调度机制重新启动我们的线程线程被堵塞后便有一些原因造成它不能继续运行 下面这个例子展示了进入堵塞状态的全部五种途径它们全都存在于名为Blockingjava的一个文件中但在这儿采用散落的片断进行解释(大家可注意到片断前后的Continued以及Continuing标志利用第章介绍的工具可将这些片断连结到一起)首先让我们看看基本的框架 //: Blockingjava // Demonstrates the various ways a thread // can be blocked import javaawt*; import javaawtevent*; import javaapplet*; import javaio*; //////////// The basic framework /////////// class Blockable extends Thread { private Peeker peeker; protected TextField state = new TextField(); protected int i; public Blockable(Container c) { cadd(state); peeker = new Peeker(this c); } public synchronized int read() { return i; } protected synchronized void update() { statesetText(getClass()getName() + state: i = + i); } public void stopPeeker() { // peekerstop(); Deprecated in Java peekerterminate(); // The preferred approach } } class Peeker extends Thread { private Blockable b; private int session; private TextField status = new TextField(); private boolean stop = false; public Peeker(Blockable b Container c) { cadd(status); thisb = b; start(); } public void terminate() { stop = true; } public void run() { while (!stop) { statussetText(bgetClass()getName() + Peeker + (++session) + ; value = + bread()); try { sleep(); } catch (InterruptedException e){} } } } ///:Continued Blockable类打算成为本例所有类的一个基础类一个Blockable对象包含了一个名为state的TextField(文本字段)用于显示出对象有关的信息用于显示这些信息的方法叫作update()我们发现它用getClassgetName()来产生类名而不是仅仅把它打印出来这是由于update(不知道自己为其调用的那个类的准确名字因为那个类是从Blockable衍生出来的 在Blockable中变动指示符是一个int i衍生类的run()方法会为其增值 针对每个Bloackable对象都会启动Peeker类的一个线程Peeker的任务是调用read()方法检查与自己关联的Blockable对象看看i是否发生了变化最后用它的status文本字段报告检查结果注意read()和update()都是同步的要求对象的锁定能自由解除这一点非常重要 睡眠 这个程序的第一项测试是用sleep()作出的 ///:Continuing ///////////// Blocking via sleep() /////////// class Sleeper extends Blockable { public Sleeper(Container c) { super(c); } public synchronized void run() { while(true) { i++; update(); try { sleep(); } catch (InterruptedException e){} } } } class Sleeper extends Blockable { public Sleeper(Container c) { super(c); } public void run() { while(true) { change(); try { sleep(); } catch (InterruptedException e){} } } public synchronized void change() { i++; update(); } } ///:Continued 在Sleeper中整个run()方法都是同步的我们可看到与这个对象关联在一起的Peeker可以正常运行直到我们启动线程为止随后Peeker便会完全停止这正是堵塞的一种形式因为Sleeperrun()是同步的而且一旦线程启动它就肯定在run()内部方法永远不会放弃对象锁定造成Peeker线程的堵塞 Sleeper通过设置不同步的运行提供了一种解决方案只有change()方法才是同步的所以尽管run()位于sleep()内部Peeker仍然能访问自己需要的同步方法——read()在这里我们可看到在启动了Sleeper线程以后Peeker会持续运行下去 暂停和恢复 这个例子接下来的一部分引入了挂起或者暂停(Suspend)的概述Thread类提供了一个名为suspend()的方法可临时中止线程以及一个名为resume()的方法用于从暂停处开始恢复线程的执行显然我们可以推断出resume()是由暂停线程外部的某个线程调用的在这种情况下需要用到一个名为Resumer(恢复器)的独立类演示暂停/恢复过程的每个类都有一个相关的恢复器如下所示 ///:Continuing /////////// Blocking via suspend() /////////// class SuspendResume extends Blockable { public SuspendResume(Container c) { super(c); new Resumer(this); } } class SuspendResume extends SuspendResume { public SuspendResume(Container c) { super(c);} public synchronized void run() { while(true) { i++; update(); suspend(); // Deprecated in Java } } } class SuspendResume extends SuspendResume { public SuspendResume(Container c) { super(c);} public void run() { while(true) { change(); suspend(); // Deprecated in Java } } public synchronized void change() { i++; update(); } } class Resumer extends Thread { private SuspendResume sr; public Resumer(SuspendResume sr) { thissr = sr; start(); } public void run() { while(true) { try { sleep(); } catch (InterruptedException e){} srresume(); // Deprecated in Java } } } ///:Continued SuspendResume也提供了一个同步的run()方法同样地当我们启动这个线程以后就会发现与它关联的Peeker进入堵塞状态等候对象锁被释放但那永远不会发生和往常一样这个问题在SuspendResume里得到了解决它并不同步整个run()方法而是采用了一个单独的同步change()方法 对于Java 大家应注意suspend()和resume()已获得强烈反对因为suspend()包含了对象锁所以极易出现死锁现象换言之很容易就会看到许多被锁住的对象在傻乎乎地等待对方这会造成整个应用程序的凝固尽管在一些老程序中还能看到它们的蹤迹但在你写自己的程序时无论如何都应避免本章稍后就会讲述正确的方案是什么 等待和通知 通过前两个例子的实践我们知道无论sleep()还是suspend()都不会在自己被调用的时候解除锁定需要用到对象锁时请务必注意这个问题在另一方面wait()方法在被调用时却会解除锁定这意味着可在执行wait()期间调用线程对象中的其他同步方法但在接着的两个类中我们看到run()方法都是同步的在wait()期间Peeker仍然拥有对同步方法的完全访问权限这是由于wait()在挂起内部调用的方法时会解除对象的锁定 我们也可以看到wait()的两种形式第一种形式采用一个以毫秒为单位的参数它具有与sleep()中相同的含义暂停这一段规定时间区别在于在wait()中对象锁已被解除而且能够自由地退出wait()因为一个notify()可强行使时间流逝 第二种形式不采用任何参数这意味着wait()会持续执行直到notify()介入为止而且在一段时间以后不会自行中止 wait()和notify()比较特别的一个地方是这两个方法都属于基础类Object的一部分不象 |