线程为何会堵塞

堵塞状态是前述四种状态中最有趣的,值得我们作进一步的探讨。线程被堵塞可能是由下述五方面的原因造成的:
(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会被堵塞,并停止运行。弄懂这一点,将有助于我们加深对“堵塞”这一概念的认识。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索对象
, 线程
, 方法
, ios c/c++
, 同步
, public
, 线程java堵塞
, 线程,C
, 一个
SUSPEND
线程堵塞、weblogic 线程堵塞、ios 主线程堵塞 动画、java线程堵塞、ios 线程堵塞,以便于您获取更多的相关知识。

时间: 2024-12-22 10:16:27

线程为何会堵塞的相关文章

线程的优先级

线程的优先级(Priority)告诉调试程序该线程的重要程度有多大.如果有大量线程都被堵塞,都在等候运行,调试程序会首先运行具有最高优先级的那个线程.然而,这并不表示优先级较低的线程不会运行(换言之,不会因为存在优先级而导致死锁).若线程的优先级较低,只不过表示它被准许运行的机会小一些而已. 可用getPriority()方法读取一个线程的优先级,并用setPriority()改变它.在下面这个程序片中,大家会发现计数器的计数速度慢了下来,因为它们关联的线程分配了较低的优先级:   //: Co

java的线程死锁

由于线程可能进入堵塞状态,而且由于对象可能拥有"同步"方法--除非同步锁定被解除,否则线程不能访问那个对象--所以一个线程完全可能等候另一个对象,而另一个对象又在等候下一个对象,以此类推.这个"等候"链最可怕的情形就是进入封闭状态--最后那个对象等候的是第一个对象!此时,所有线程都会陷入无休止的相互等待状态,大家都动弹不得.我们将这种情况称为"死锁".尽管这种情况并非经常出现,但一旦碰到,程序的调试将变得异常艰难. 就语言本身来说,尚未直接提供防

Java线程的讨论与应用

一.为什么要研究和使用线程 一般来说,计算机正在执行的程序称作进程(process),进程有不同的地址空间并且是在同一系统上运行的不同程序,如WORD和Excel,进程间的通讯是很费时而且有限的.上下文切换.改变运行的进程也是非常复杂的.进程间通讯复杂,可能需要管道.消息队列.共享内存(sharedmemory)或信号处理来保证进程间的通讯.尽管许多程序都在运行,但一次只能与一个程序打交道. 线程(thread)是指进程中单一顺序的控制流.又称为轻量级进程.线程则共享相同的地址空间并共同构成一个

Python中线程编程之threading模块的使用详解

  这篇文章主要介绍了Python中线程编程之threading模块的使用详解,由于GIL的存在,线程一直是Python编程中的焦点问题,需要的朋友可以参考下 threading.Thread Thread 是threading模块中最重要的类之一,可以使用它来创建线程.有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法;另一种是创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入.下面分别举例说明.先来看看通过继承th

深入解析Java并发程序中线程的同步与线程锁的使用_java

synchronized关键字 synchronized,我们谓之锁,主要用来给方法.代码块加锁.当某个方法或者代码块使用synchronized时,那么在同一时刻至多仅有有一个线程在执行该段代码.当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段.但是,其余线程是可以访问该对象中的非加锁代码块的. synchronized主要包括两种方法:synchronized 方法.synchronized 块. synchron

使用ADO封装类的数据库程序开发实例[第二版](下)

CAdoRecordSet class: CAdoRecordSet: CAdoRecordSet::CAdoRecordSet() CAdoRecordSet::CAdoRecordSet(CAdoConnection *pConnection) void CAdoRecordSet::SetAdoConnection(CAdoConnection *pConnection) 创建Connection对象. Params: [pConnection]: 连接对象指针. Open 方法: BOO

详解Python中threading模块的几个常用方法

  这篇文章主要介绍了举例详解Python中threading模块的几个常用方法,threading模块用来创建和操作线程,是Python学习当中的重要知识,需要的朋友可以参考下 threading.Thread Thread 是threading模块中最重要的类之一,可以使用它来创建线程.有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法;另一种是创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入.下面分别举例说明.

关于多线程中使用SendMessage

如果,窗口是由非主线程创建的,那么,那个线程将会跑一个自己的窗口消息队列.调用SendMessage会切换到该线程上去执行. 主线程是个窗口线程,在另外一个工作线程中调用了窗口线程的SendMessage,那么执行的过程是: 1.工作线程挂起 2.切换到窗口线程,执行对应的消息处理 3.切换回工作线程 SendMessage : 如果指定窗口由调用线程创建,那么窗口过程会被当成一个子程序立即调用.如果指定窗口由另外一个线程创建,那么系统会切换到那个线程,并且调用合适的窗口过程.在线程之间传递的消

写日志的那些事儿

写日志简介 一般提到写日志,主要有下面几种不同的场景: 诊断日志:应用打印异常信息,排查问题用,一般是给人看的,输出格式会比较随意,里面可能会有异常堆栈,或者排查问题用的一些文本信息: 数据日志:一般是用来做监控和数据分析的,可以人肉临时分析,也可以给机器分析,要求格式比较固定: 交易日志:一般在日志式文件系统.NoSQL.DB 中使用,一般有 journaling,WAL(write-ahead logging),binlog.这种日志通常都不是给人看的. EagleEye 写的日志,是数据日