当前位置:七道奇文章资讯编程技术Java编程
日期:2011-03-22 16:16:00  来源:本站整理

线程为什么会堵塞[Java编程]

赞助商链接



  本文“线程为什么会堵塞[Java编程]”是由七道奇为您精心收集,来源于网络转载,文章版权归文章作者所有,本站不对其观点以及内容做任何评价,请读者自行判断,以下是其具体内容:
堵塞状况是前述四种状况中最风趣的,值得我们作进一步的探究.线程被堵塞大概是由下述五方面的缘由造成的:
(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的一部份,不象sleep(),suspend()以及resume()那样属于Thread的一部份.固然这表面看有点儿奇特——竟然让专门举行线程处理的东西成为通用底子类的一部份——但细心想想又会豁然,因为它们操作的对象锁也属于每个对象的一部份.因此,我们可将一个wait()置入任何同步办法内部,无论在那个类里能否预备举行触及线程的处理.事实上,我们能调用wait()的唯一地方是在一个同步的办法或代码块内部.若在一个差别步的办法内调用wait()大概notify(),固然程序仍旧会编译,但在运行它的时刻,就会得到一个IllegalMonitorStateException(不法监督器状况违例),并且会呈现多少有点莫名其妙的一条消息:“current thread not owner”(当前线程不是全部人”.注意sleep(),suspend()以及resume()都能在差别步的办法内调用,因为它们不需求对锁定举行操作.
只能为自己的锁定调用wait()和notify().一样地,仍旧可以编译那些试牟利用错误锁定的代码,但和平常一样会产生一样的IllegalMonitorStateException违例.我们没办法用其他人的对象锁来玩弄系统,但可要求另一个对象履行呼应的操作,对它自己的锁举行操作.所以一种做法是成立一个同步办法,令其为自己的对象调用notify().但在Notifier中,我们会看到一个同步办法内部的notify():
synchronized(wn2) {
  wn2.notify();
}

此中,wn2是范例为WaitNotify2的对象.固然并不属于WaitNotify2的一部份,这个办法仍旧得到了wn2对象的锁定.在这个时刻,它为wn2调用notify()是合理的,不会得到IllegalMonitorStateException违例.
///:Continuing
/////////// Blocking via wait() ///////////
class WaitNotify1 extends Blockable {
  public WaitNotify1(Container c) { super(c); }
  public synchronized void run() {
    while(true) {
      i++;
      update();
       try {
        wait(1000);
      } catch (InterruptedException e){}
    }
  }
}

class WaitNotify2 extends Blockable {
  public WaitNotify2(Container c) {
    super(c);
    new Notifier(this); 
  }
  public synchronized void run() {
    while(true) {
      i++;
      update();
       try {
        wait();
      } catch (InterruptedException e){}
    }
  }
}

class Notifier extends Thread {
  private WaitNotify2 wn2;
  public Notifier(WaitNotify2 wn2) {
    this.wn2 = wn2;
    start();
  }
  public void run() {
    while(true) {
       try {
        sleep(2000);
      } catch (InterruptedException e){}
      synchronized(wn2) {
        wn2.notify();
      }
    }
  }
} ///:Continued

若必须等候其他某些条件(从线程外部加以掌握)发生改变,同时又不想在线程内一向傻乎乎地等下去,普通就需求用到wait().wait()答应我们将线程置入“就寝”状况,同时又“主动”地等候条件发生改变.并且只有在一个notify()或notifyAll()发生改变的时刻,线程才会被唤醒,并查抄条件能否有变.因此,我们认为它供应了在线程间举行同步的一种手段.

4. IO堵塞
若一个数据流必须等候一些IO活动,便会自动进入“堵塞”状况.在本例下面列出的部份中,有两个类协同通用的Reader以及Writer对象工作(利用Java 1.1的流).但在测试模子中,会设置一个管道化的数据流,使两个线程彼此间能安全地传送数据(这恰是利用管道流的目的).
Sender将数据置入Writer,并“就寝”随机长短的时间.但是,Receiver本身并没有包含sleep(),suspend()大概wait()办法.但在履行read()的时刻,假如没有数据存在,它会自动进入“堵塞”状况.以下所示:
///:Continuing
class Sender extends Blockable { // send
  private Writer out;
  public Sender(Container c, Writer out) { 
    super(c);
    this.out = out; 
  }
  public void run() {
    while(true) {
      for(char c = 'A'; c <= 'z'; c++) {
         try {
          i++;
          out.write(c);
          state.setText("Sender sent: " 
            + (char)c);
          sleep((int)(3000 * Math.random()));
        } catch (InterruptedException e){}
          catch (IOException e) {}
      }
    }
  }
}

class Receiver extends Blockable {
  private Reader in;
  public Receiver(Container c, Reader in) { 
    super(c);
    this.in = in; 
  }
  public void run() {
    try {
      while(true) {
        i++; // Show peeker it's alive
        // Blocks until characters are there:
        state.setText("Receiver read: "
          + (char)in.read());
      }
    } catch(IOException e) { e.printStackTrace();}
  }
} ///:Continued

这两个类也将信息送入自己的state字段,并改正i值,使Peeker知道线程仍在运行.

5. 测试
令人惊奇的是,主要的程序片(Applet)类非常简单,这是大大都工作都已置入Blockable框架的来由.大约地说,我们成立了一个由Blockable对象构成的数组.并且由于每个对象都是一个线程,所以在按下“start”按钮后,它们会采纳自己的行动.还有另一个按钮和actionPerformed()从句,用于中止全部Peeker对象.由于Java 1.2“反对”利用Thread的stop()办法,所以可考虑采取这种折衷情势的中止方法.
为了在Sender和Receiver之间成立一个衔接,我们成立了一个PipedWriter和一个PipedReader.注意PipedReader in必须通过一个构建器参数同PipedWriterout衔接起来.在那今后,我们在out内放进去的全部东西都可从in中提取出来——仿佛那些东西是通过一个“管道”传输过去的.随后将in和out对象额外传送给Receiver和Sender构建器;后者将它们当作肆意范例的Reader和Writer对待(也就是说,它们被“上溯”造型了).
Blockable句柄b的数组在定义之初并未得到初始化,因为管道化的数据流是不可在定义前设置好的(对try块的需求将成为障碍):
///:Continuing
/////////// Testing Everything ///////////
public class Blocking extends Applet {
  private Button 
    start = new Button("Start"),
    stopPeekers = new Button("Stop Peekers");
  private boolean started = false;
  private Blockable[] b;
  private PipedWriter out;
  private PipedReader in;
  public void init() {
     out = new PipedWriter();
    try {
      in = new PipedReader(out);
    } catch(IOException e) {}
    b = new Blockable[] {
      new Sleeper1(this),
      new Sleeper2(this),
      new SuspendResume1(this),
      new SuspendResume2(this),
      new WaitNotify1(this),
      new WaitNotify2(this),
      new Sender(this, out),
      new Receiver(this, in)
    };
    start.addActionListener(new StartL());
    add(start);
    stopPeekers.addActionListener(
      new StopPeekersL());
    add(stopPeekers);
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(!started) {
        started = true;
        for(int i = 0; i < b.length; i++)
          b[i].start();
      }
    }
  }
  class StopPeekersL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      // Demonstration of the preferred 
      // alternative to Thread.stop():
      for(int i = 0; i < b.length; i++)
        b[i].stopPeeker();
    }
  }
  public static void main(String[] args) {
    Blocking applet = new Blocking();
    Frame aFrame = new Frame("Blocking");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(350,550);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

在init()中,注意循环会遍历整个数组,并为页增添state和peeker.status文本字段.
初次成立好Blockable线程今后,每个这样的线程城市自动成立并启动自己的Peeker.所以我们会看到各个Peeker都在Blockable线程启动之前运行起来.这一点非常重要,因为在Blockable线程启动的时刻,部份Peeker会被堵塞,并终止运行.弄懂这一点,将有助于我们加深对“堵塞”这一概念的熟习.
  以上是“线程为什么会堵塞[Java编程]”的内容,如果你对以上该文章内容感兴趣,你可以看看七道奇为您推荐以下文章:
  • 线程为什么会堵塞
  • 本文地址: 与您的QQ/BBS好友分享!
    • 好的评价 如果您觉得此文章好,就请您
        0%(0)
    • 差的评价 如果您觉得此文章差,就请您
        0%(0)

    文章评论评论内容只代表网友观点,与本站立场无关!

       评论摘要(共 0 条,得分 0 分,平均 0 分) 查看完整评论
    Copyright © 2020-2022 www.xiamiku.com. All Rights Reserved .