首页 > 学院 > 开发设计 > 正文

为何会堵塞

2019-11-18 13:23:06
字体:
来源:转载
供稿:网友

  堵塞状态是前述四种状态中最有趣的,值得我们作进一步的探讨。线程被堵塞可能是由下述五方面的原因造成的:
  (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.
  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 TextField(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衍生出来的。
  在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(恢复器)的独立类。演示暂停/恢复过程的每个类都有一个相关的恢复器。如下所示:
  ///: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()方法。同样地,当我们启动这个线程以后,就会发现与它关联的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的一部分,不象

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表