`

java中,如何安全的结束一个正在运行的线程?

 
阅读更多

http://blog.tianya.cn/blogger/post_read.asp?BlogID=3668353&PostID=32699671

 

问题
  Java中提供了很多调度线程的方法,上一节介绍了其中一种控制线程的方法:如何等待一个线程结束。那么如果不希望等待线程结束,而是根据问题的需要随时都要中断线程使其结束,这种对线程的控制方法该如何实现呢?
  解决思路
  首先必须先明确“中断”这个概念的实际含义,这里的中断是指一个线程在其任务完成之前被强行停止,提前消亡的过程。查阅JDK的帮助文档,可以找到这样一个和中断有关的方法:interrupt()。
  它的语法格式如下所示:
  public void interrupt()
  该方法的功能是中断一个线程的执行。但是,在实际使用当中发现,这个方法不一定能够真地中断一个正在运行的线程。下面通过一个例子来看一看使用interrput()方法中断一个线程时所出现的结果。程序代码如下所示:
  // 例4.4.1 InterruptThreadDemo.java
  class MyThread extends Thread
  {
  public void run()
  {
  while(true) // 无限循环,并使线程每隔1秒输出一次字符串
  { 
  System.out.println(getName()+' is running'); 
  try{ 
  sleep(1000);
  }catch(InterruptedException e){
  System.out.println(e.getMessage());
  }
  }
  }
  }
  class InterruptThreadDemo
  {
  public static void main(String[] args) throws InterruptedException
  {
  MyThread m=new MyThread(); // 创建线程对象m
  System.out.println('Starting thread...');
  m.start(); // 启动线程m
  Thread.sleep(2000); //主线程休眠2秒,使线程m一直得到执行
  System.out.println('Interrupt thread...');
  m.interrupt(); // 调用interrupt()方法中断线程m
  Thread.sleep(2000); // 主线程休眠2秒,观察中断后的结果
  System.out.println('Stopping application...'); // 主线程结束
  }
  }
  这个程序的本意是希望,当程序执行到m.interrupt()方法后,线程m将被中断并进入消亡状态。然而运行这个程序,屏幕里显示了出人意料的结果,如图4.4.1所示。
  通过对结果的分析,可以发现,用户线程在调用了interrupt()方法之后并没有被中断,而是继续执行,直到人为地按下Ctrl+C或者Pause键为止。这个例子说明一个事实,直接使用interrput()方法并不能中断一个正在运行的线程。那么用什么样的方法才能中断一个正在运行的线程呢?
  
  图 4.4.1 对线程调用了interrupt()
  通过查阅JDK,有些读者可能会看到Thread类中所提供的stop()方法。但是在这里需要强调的是,虽然该方法确实能够停止一个正在运行的线程,但是该方法是不安全的,因为有时使用它会导致严重的系统错误。例如一个线程正在等待关键的数据结构,并只完成了部分地改变,如果在这一时刻停止该线程,那么数据结构将会停留在错误的状态上。正因为如此,在Java后期的版本中,它将不复存在。因此,使用stop()方法来中断一个线程是不合适的。
  这时我们想到了使用共享变量的方式,通过一个共享信号变量来通知线程是否需要中断,如果需要中断,则停止正在执行的任务,否则让任务继续执行。这种方式是如何实现的呢?
  具体步骤
  在这种方式中,之所以引入共享变量,是因为该变量可以被多个执行相同任务的线程用来作为是否中断的信号,通知中断线程的执行。下面通过在程序中引入共享变量来改进前面例4.4.1,改进后的代码如下所示:
  // 例4.4.2 InterruptThreadDemo2.java
  class MyThread extends Thread
  {
  boolean stop = false; // 引入一个布尔型的共享变量stop
  public void run()
  {
  while(!stop) // 通过判断stop变量的值来确定是否继续执行线程体
  {
  System.out.println(getName()+' is running');
  try
  {
  sleep(1000); 
  }catch(InterruptedException e){
  System.out.println(e.getMessage());
  }
  }
  System.out.println('Thread is exiting...');
  }
  }
  class InterruptThreadDemo2
  {
  public static void main(String[] args) throws InterruptedException
  {
  MyThread m=new MyThread();
  System.out.println('Starting thread...');
  m.start();
  Thread.sleep(3000); 
  System.out.println('Interrupt thread...');
  m.stop=true; // 修改共享变量
  Thread.sleep(3000); // 主线程休眠以观察线程中断后的情况
  System.out.println('Stopping application...');
  }
  }
  在使用共享变量来中断一个线程的过程中,线程体通过循环来周期性的检查这一变量的状态。如果变量的状态改变,说明程序发出了立即中断该线程的请求,此时,循环体条件不再满足,结束循环,进而结束线程的任务。程序执行的结果如图4.4.2所示:
  
  图4.4.2 引入共享变量来中断线程
  其中,主程序中的第二个Thread.sleep(3000);语句就是用来使程序不提早结束,以便观察线程m的中断情况。结果是一旦将共享变量stop设置为true,则中断立即发生。
  为了更加安全起见,通常需要将共享变量定义为volatile类型或者将对该共享变量的一切访问封装到同步的代码或者同步方法中去。后者所提到的技术将在第4.5节中介绍。
  在多线程的程序中,当出现有两个或多个线程共享同一实例变量的情况时,每一个线程可以保持这个实例变量自己的私有副本,变量的实际备份在不同时间被更新。而问题就是变量的主备份总是需要反映它的当前状态,此时反而使效率降低。为保证效率,只需要简单地指定变量为volatile类型即可,它可以告诉编译器必须总是使用volatile变量的主备份(或者至少总是保持任何私有的备份和最新的备份一样,反之亦然)。同样,对主变量的访问必须同任何私有备份一样,精确地顺序执行。
  如果需要一次中断所有由同一线程类创建的线程,该怎样实现呢?有些读者可能马上就想到了对每一个线程对象通过设置共享变量的方式来中断线程。这种方法当然可以,那么有没有更好的方法呢?
  此时只需将共享变量设置为static类型的即可。然后在主程序中当需要中断所有同一个线程类创建的线程对象时,使用MyThread.stop=true;语句就可实现对所有同一个线程类创建的线程对象的中断操作,而且效率明显提高。读者不妨试一试。
  专家说明
  通过本节介绍了如何中断一个正在执行的线程,既不是用stop()方法,也不是用interrupt()方法,而是通过引入了共享变量的形式有效地解决了线程中断的问题。其实这种方法有很多好处,它避免了一些无法想象的意外情况的发生,特别是将共享变量所访问的一切代码都封装到同步方法中以后,安全性将更高。在本节中,还可以尝试创建多个线程来检验这种中断方式的好处。此外,还介绍了volatile类型说明符的作用,这更加有助于提高中断线程的效率,值得提倡。
  专家指点
  本小节不仅要掌握如何使用共享变量的方法来中断一个线程,还要明白为什么使用其他方法来中断线程就不安全。其实,在多线程的调度当中还会出现一个问题,那就是死锁。死锁的出现将导致线程间均无法向前推进,从而陷入尴尬的局面。因此,为减少出现死锁的发生,Java 1.2以后的版本中已经不再使用Thread类的stop(),suspend(),resume()以及destroy()方法。特别是不安全的stop()方法,原因就是它会解除由线程获取的所有锁定,而且一旦对象处于一种不连贯的状态,那么其他线程就能在那种状态下检查和修改它们,结果导致很难再检查出问题的真正所在。因此最好的方法就是,用一个标志来告诉线程什么时候应该退出自己的run()方法,并中断自己的执行。通过后面小节的学习将会更好的理解这个问题。
  相关问题
  如果一个线程由于等待某些事件的发生而被阻塞,又该如何实现该线程的中断呢?比如当一个线程由于需要等候键盘输入而被阻塞,处于不可运行状态时,即使主程序中将该线程的共享变量设置为true,但该线程此时根本无法检查循环标志,当然也就无法立即中断。
  其实,这种情况经常会发生,比如调用Thread.join()方法,或者Thread.sleep()方法,在网络中调用ServerSocket.accept()方法,或者调用了DatagramSocket.receive()方法时,都有可能导致线程阻塞。即便这样,仍然不要使用stop()方法,而是使用Thread提供的interrupt()方法,因为该方法虽然不会中断一个正在运行的线程,但是它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态,退出堵塞代码。
  下面看一个例子来说明这个问题:
  // 例4.4.3 InterruptThreadDemo3.java
  class MyThread extends Thread
  {
  volatile boolean stop = false;
  public void run()
  {
  while(!stop)
  {
  System.out.println(getName()+' is running');
  try
  {
  sleep(1000);
  }catch(InterruptedException e){ 
  System.out.println('week up from blcok...');
  stop=true; // 在异常处理代码中修改共享变量的状态
  }
  }
  System.out.println(getName()+' is exiting...');
  }
  }
  class InterruptThreadDemo3
  {
  public static void main(String[] args) throws InterruptedException
  {
  MyThread m1=new MyThread();
  System.out.println('Starting thread...');
  m1.start();
  Thread.sleep(3000); 
  System.out.println('Interrupt thread...:'+m1.getName());
  m1.stop=true; // 设置共享变量为true
  m1.interrupt(); // 阻塞时退出阻塞状态
  Thread.sleep(3000); // 主线程休眠3秒以便观察线程m1的中断情况
  System.out.println('Stopping application...');
  }
  }
  程序中如果线程m1发生了阻塞,那么虽然执行了m1.stop=true;语句,但是stop的值并未改变。为了能够中断该线程,必须在异常处理语句中对共享变量的值进行重新设置,从而实现了在任何情况下都能够中断线程的目的。
  一定要记住,m1.interrupt();语句只有当线程发生阻塞时才有效。它的作用就是抛出一个InterruptedException类的异常对象,使try…catch语句捕获异常,并对其进行处理。请读者仔细研究这个程序,以便能够看出其中的巧妙之处。

分享到:
评论

相关推荐

    Java多线程编程指南

    Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,...一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

    java多线程编程 在主线程main中创建两个子线程

    自己学着编写的一个JAVA多线程程序,该程序实现的功能是:在主线程main中创建两个子线程,A和B,线程A先运行,再运行B线程,当两个子线程都运行完毕后,才运行主线程,并最终结束整个程序的运行。 希望该程序对初学...

    java多线程下载

    1.得到服务器下载文件的大小,然后在本地设置一个临时文件和服务器端文件大小一致 a)获得访问网络地址 b)通过URL对象的openConnection()方法打开连接,返回一个连接...b)当没有正在运行的线程时切文件存在时删除文件

    Java实现终止线程池中正在运行的定时任务

    本篇文章给大家分享了JAVA中实现终止线程池中正在运行的定时任务的具体步骤和方法,有需要的朋友跟着学习下。

    操作系统——实验四 windows中线程的创建和同步控制

    在windows的环境下,创建一个控制台进程,此进程创建两个并发线程,一个是读线程,另一个是写线程。这两个线程共享一个数组A,写线程对数组分别进行10次写操作,每次写操作对A的每个元素赋一个相同的值;读线程对数组...

    Java虚拟机(第二版)

    一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止。你在同一台机器上运行三个程序,就会有三个运行中的Java虚拟机。 Java虚拟机总是开始于一个main()方法,这...

    Java开发技术大全(500个源代码).

    invokeMethod.java 同一个类中调用方法示例 invokeOther.java 类的外部调用方法示例 invokeStaticMethod.java 调用静态方法示例 localVariable.java 演示局部变量 localVSmember.java 局部变量与成员变量同名...

    java实现内存动态分配

    每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈。进程所创建的所有类的实例(也...

    可运行的贪吃蛇java小程序

    可以用java运行的贪吃蛇小程序 Button snake[]; //定义蛇按钮 int shu=0; //蛇的节数 int food[]; //食物数组 boolean result=true; //判定结果是输 还是赢 Thread thread; //定义线程 static int weix,weiy; /...

    java 面试题 总结

    与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。...

    Java线程之join_动力节点Java学院整理

    join() 定义在Thread.java中。 join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。这句话可能有点晦涩,我们还是通过例子去理解

    Java多线程——Thread类中run()方法和start()方法的区别.docx

    通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到spu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行...

    超级有影响力霸气的Java面试题大全文档

    与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    1.6 小结:我们学会了编译和运行一个Java程序! 18 1.7 习题 19 第2章 搭建自己的集成开发环境 20 教学视频:31分钟 2.1 安装集成开发环境 20 2.1.1 集成开发环境有哪些 20 2.1.2 安装Eclipse 21 2.2 Eclipse...

    多线程实现的Java爬虫程序

     抓取时也会在程序源文件目录下生成一个report.txt文件记录爬虫的运行情况,并在抓取结束后生成一个fileindex.txt文件维护网页文件索引。  本程序用到了多线程(静态变量和同步),泛型,文件操作,URL类和连接,...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    1.6 小结:我们学会了编译和运行一个Java程序! 18 1.7 习题 19 第2章 搭建自己的集成开发环境 20 教学视频:31分钟 2.1 安装集成开发环境 20 2.1.1 集成开发环境有哪些 20 2.1.2 安装Eclipse 21 2.2 Eclipse...

    java向多线程中传递参数的三种方法详细介绍

    但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据

    Java核心面试题汇总(最新版)

    这里所说的线程指程序执行过程中的一个线程实体。JVM 允许一个应用并发执行多个线程。 Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系。当线程本地存储、缓 冲区分配、同步对象、栈、程序计数器等...

    windows 系统下启动与结束java的jar包的脚本(包括如何设置进程名称)

    windows 系统下启动与结束java的jar包的脚本(包括如何设置进程名称)

Global site tag (gtag.js) - Google Analytics