如何终止java线程

来源:互联网 发布:网络乌托邦啥意思 编辑:程序博客网 时间:2024/05/16 19:36

如何终止java线程

 

   终止线程的三种方法 

    有三种方法可以使终止线程。 

    1.  使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 

    2.  使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。 

    3.  使用interrupt方法中断线程。 
1. 使用退出标志终止线程 

    当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的。如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。下面给出了一个利用退出标志终止线程的例子。 

package chapter2; 

public class ThreadFlag extends Thread 

    public volatile boolean exit = false; 

    public void run() 
    { 
        while (!exit); 
    } 
    public static void main(String[] args) throws Exception 
    { 
        ThreadFlag thread = new ThreadFlag(); 
        thread.start(); 
        sleep(5000); // 主线程延迟5秒 
        thread.exit = true;  // 终止线程thread 
        thread.join(); 
        System.out.println("线程退出!"); 
    } 



    在上面代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值, 

    2. 使用stop方法终止线程 

    使用stop方法可以强行终止正在运行或挂起的线程。我们可以使用如下的代码来终止线程: 

thread.stop(); 


    虽然使用上面的代码可以终止线程,但使用stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程。 

    3. 使用interrupt方法终止线程 

    使用interrupt方法来终端线程可分为两种情况: 

    (1)线程处于阻塞状态,如使用了sleep方法。 

    (2)使用while(!isInterrupted()){……}来判断线程是否被中断。 

    在第一种情况下使用interrupt方法,sleep方法将抛出一个InterruptedException例外,而在第二种情况下线程将直接退出。下面的代码演示了在第一种情况下使用interrupt方法。 

package chapter2; 

public class ThreadInterrupt extends Thread 

    public void run() 
    { 
        try 
        { 
            sleep(50000);  // 延迟50秒 
        } 
        catch (InterruptedException e) 
        { 
            System.out.println(e.getMessage()); 
        } 
    } 
    public static void main(String[] args) throws Exception 
    { 
        Thread thread = new ThreadInterrupt(); 
        thread.start(); 
        System.out.println("在50秒之内按任意键中断线程!"); 
        System.in.read(); 
        thread.interrupt(); 
        thread.join(); 
        System.out.println("线程已经退出!"); 
    } 



    上面代码的运行结果如下: 

    在50秒之内按任意键中断线程! 

    sleep interrupted 
    线程已经退出! 


    在调用interrupt方法后, sleep方法抛出异常,然后输出错误信息:sleep interrupted. 

    注意:在Thread类中有两个方法可以判断线程是否通过interrupt方法被终止。一个是静态的方法interrupted(),一个是非静态的方法isInterrupted(),这两个方法的区别是interrupted用来判断当前线是否被中断,而isInterrupted可以用来判断其他线程是否被中断。因此,while (!isInterrupted())也可以换成while (!Thread.interrupted())。 

如何停止JAVA线程

如何停止java的线程一直是一个困恼我们开发多线程程序的一个问题。这个问题最终在Java5的java.util.concurrent中得到了回答:使用interrupt(),让线程在run方法中停止。

简介

在Java的多线程编程中,java.lang.Thread类型包含了一些列的方法start(), stop(), stop(Throwable) and suspend(), destroy() and resume()。通过这些方法,我们可以对线程进行方便的操作,但是这些方法中,只有start()方法得到了保留。

在Sun公司的一篇文章《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? 》中详细讲解了舍弃这些方法的原因。那么,我们究竟应该如何停止线程呢?

建议使用的方法

在《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? 》中,建议使用如下的方法来停止线程:

    private volatile Thread blinker; 
    public void stop() { 
        blinker = null
    } 
    public void run() { 
        Thread thisThread = Thread.currentThread(); 
        while (blinker == thisThread) { 
            try { 
                thisThread.sleep(interval); 
            } catch (InterruptedException e){ 
            } 
            repaint(); 
        } 
    }

关于使用volatile关键字的原因,请查看http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930。

当线程处于非运行(Run)状态

当线程处于下面的状况时,属于非运行状态:

  • 当sleep方法被调用。

  • 当wait方法被调用。

  • 当被I/O阻塞,可能是文件或者网络等等。

当线程处于上述的状态时,使用前面介绍的方法就不可用了。这个时候,我们可以使用interrupt()来打破阻塞的情况,如:

public void stop() {        Thread tmpBlinker = blinker;        blinker = null;        if (tmpBlinker != null) {           tmpBlinker.interrupt();        }    }

当interrupt()被调用的时候,InterruptedException将被抛出,所以你可以再run方法中捕获这个异常,让线程安全退出:

try {   ....   wait();} catch (InterruptedException iex) {   throw new RuntimeException("Interrupted",iex);}

阻塞的I/O

当线程被I/O阻塞的时候,调用interrupt()的情况是依赖与实际运行的平台的。在Solaris和Linux平台上将会抛出InterruptedIOException的异常,但是Windows上面不会有这种异常。所以,我们处理这种问题不能依靠于平台的实现。如:

package com.cnblogs.gpcusterimport java.net.*;import java.io.*;public abstract class InterruptibleReader extends Thread {    private Object lock = new Object( );    private InputStream is;    private boolean done;    private int buflen;    protected void processData(byte[] b, int n) { }    class ReaderClass extends Thread {        public void run( ) {            byte[] b = new byte[buflen];            while (!done) {                try {                    int n = is.read(b, 0, buflen);                    processData(b, n);                } catch (IOException ioe) {                    done = true;                }            }            synchronized(lock) {                lock.notify( );            }        }    }    public InterruptibleReader(InputStream is) {        this(is, 512);    }    public InterruptibleReader(InputStream is, int len) {        this.is = is;        buflen = len;    }    public void run( ) {        ReaderClass rc = new ReaderClass( );        synchronized(lock) {            rc.start( );            while (!done) {                try {                    lock.wait( );                } catch (InterruptedException ie) {                    done = true;                    rc.interrupt( );                    try {                        is.close( );                    } catch (IOException ioe) {}                }            }        }    }}

另外,我们也可以使用InterruptibleChannel接口。 实现了InterruptibleChannel接口的类可以在阻塞的时候抛出ClosedByInterruptException。如:

package com.cnblogs.gpcusterimport java.io.BufferedReader;import java.io.FileDescriptor;import java.io.FileInputStream;import java.io.InputStream;import java.io.InputStreamReader;import java.nio.channels.Channels;public class InterruptInput {       static BufferedReader in = new BufferedReader(            new InputStreamReader(            Channels.newInputStream(            (new FileInputStream(FileDescriptor.in)).getChannel())));        public static void main(String args[]) {        try {            System.out.println("Enter lines of input (user ctrl+Z Enter to terminate):");            System.out.println("(Input thread will be interrupted in 10 sec.)");            // interrupt input in 10 sec            (new TimeOut()).start();            String line = null;            while ((line = in.readLine()) != null) {                System.out.println("Read line:'"+line+"'");            }        } catch (Exception ex) {            System.out.println(ex.toString()); // printStackTrace();        }    }        public static class TimeOut extends Thread {        int sleepTime = 10000;        Thread threadToInterrupt = null;            public TimeOut() {            // interrupt thread that creates this TimeOut.            threadToInterrupt = Thread.currentThread();            setDaemon(true);        }                public void run() {            try {                sleep(10000); // wait 10 sec            } catch(InterruptedException ex) {/*ignore*/}            threadToInterrupt.interrupt();        }    }}

这里还需要注意一点,当线程处于写文件的状态时,调用interrupt()不会中断线程。

参考资料

How to Stop a Thread or a Task

Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?


不提倡的stop()方法 
臭名昭著的stop()停止线程的方法已不提倡使用了,原因是什么呢?
 当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,并抛出特殊的ThreadDeath()异常。这里的“立即”因为太“立即”了,
假如一个线程正在执行:

synchronized void { x = 3; y = 4;}

  由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记 线程的stop方法,以后我们再也不要说“停止线程”了。

   如何才能“结束”一个线程?

interupt()中断线程

一个线程从运行到真正的结束,应该有三个阶段:
  1. 正常运行.
  2. 处理结束前的工作,也就是准备结束.
  3. 结束退出.
那么如何让一个线程结束呢?既然不能调用stop,可用的只的interrupt()方法。但interrupt()方法只是改变了线程的运行状态,如何让它退出运行?对于一般逻辑,只要线程状态已经中断,我们就可以让它退出,这里我们定义一个线程类ThreadA,所以这样的语句可以保证线程在中断后就能结束运行:
 while(!isInterrupted()){  正常逻辑 }

,一个测试类,ThreadDemo
  这样ThreadDemo调用interrupt()方法,isInterrupted()为true,就会退出运行。但是如果线程正在执行wait,sleep,join方法,你调用interrupt()方法,这个逻辑就不完全了。
我们可以这样处理:
 public void run(){    while(!isInterrupted()){   try{    正常工作   }catch(InterruptedException e){    //nothing   }    } } }
想一想,如果一个正在sleep的线程,在调用interrupt后,会如何?wait方法检查到isInterrupted()为true,抛出异常, 而你又没有处理。而一个抛出了InterruptedException的线程的状态马上就会被置为非中断状态,如果catch语句没有处理异常,则下一 次循环中isInterrupted()为false,线程会继续执行,可能你N次抛出异常,也无法让线程停止。
这个错误情况的实例代码
ThreadA

public class ThreadA extends Thread {
   int count=0;
   public void run(){
       System.out.println(getName()+"将要运行...");
       while(!this.isInterrupted()){
           System.out.println(getName()+"运行中"+count++);
           try{
               Thread.sleep(400);
           }
catch(InterruptedException e){
               System.out.println(getName()+"从阻塞中退出...");
               System.out.println("this.isInterrupted()="+this.isInterrupted());

           }

       }

       System.out.println(getName()+"已经终止!");
   }

}

ThreadDemo
public class ThreadDemo {
    
    public static void main(String argv[])throws InterruptedException{
        ThreadA ta=new ThreadA();
        ta.setName("ThreadA");
        ta.start();
        Thread.sleep(2000);
        System.out.println(ta.getName()+"正在被中断...");
        ta.interrupt();
        System.out.println("ta.isInterrupted()="+ta.isInterrupted());
    }


}


 那么如何能确保线程真正停止?在线程同步的时候我们有一个叫“二次惰性检测”(double check),能在提高效率的基础上又确保线程真正中同步控制中。那么我把线程正确退出的方法称为“双重安全退出”,即不以isInterrupted ()为循环条件。而以一个标记作为循环条件:
正确的ThreadA代码是:
 

public class ThreadA extends Thread {
    private boolean isInterrupted=false;
   int count=0;
   
   public void interrupt(){
       isInterrupted = true;
       super.interrupt();
      }

   
   public void run(){
       System.out.println(getName()+"将要运行...");
       while(!isInterrupted){
           System.out.println(getName()+"运行中"+count++);
           try{
               Thread.sleep(400);
           }
catch(InterruptedException e){
               System.out.println(getName()+"从阻塞中退出...");
               System.out.println("this.isInterrupted()="+this.isInterrupted());

           }

       }

       System.out.println(getName()+"已经终止!");
   }

}


    在java多线程编程中,线程的终止可以说是一个必然会遇到的操作。但是这样一个常见的操作其实并不是一个能够轻而易举实现的操作,而且在某些场景下情况会变得更复杂更棘手。

        Java标准API中的Thread类提供了stop方法可以终止线程,但是很遗憾,这种方法不建议使用,原因是这种方式终止线程中断临界区代码执行,并会释放线程之前获取的监控器锁,这样势必引起某些对象状态的不一致(因为临界区代码一般是原子的,不会被干扰的),具体原因可以参考资料[1]。这样一来,就必须根据线程的特点使用不同的替代方案以终止线程。根据停止线程时线程执行状态的不同有如下停止线程的方法。

1 处于运行状态的线程停止

        处于运行状态的线程就是常见的处于一个循环中不断执行业务流程的线程,这样的线程需要通过设置停止变量的方式,在每次循环开始处判断变量是否改变为停止,以达到停止线程的目的,比如如下代码框架:

[java] view plaincopy
  1. private volatile Thread blinker;  
  2. public void stop() {  
  3.         blinker = null;  
  4. }  
  5. public void run() {  
  6.         Thread thisThread = Thread.currentThread();  
  7.         while (blinker == thisThread) {  
  8.             try {  
  9.                 //业务流程  
  10.             } catch (Exception e){}  
  11.         }  
  12. }<span style="font-size:12px;">  
  13. </span>  

        如果主线程调用该线程对象的stop方法,blinker对象被设置为null,则线程的下次循环中blinker=thisThread,因而可以退出循环,并退出run方法而使线程结束。将引用变量blinker的类型前加上volatile关键字的目的是防止编译器对该变量存取时的优化,这种优化主要是缓存对变量的修改,这将使其他线程不会立刻看到修改后的blinker值,从而影响退出。此外,Java标准保证被volatile修饰的变量的读写都是原子的。

        上述的Thread类型的blinker完全可以由更为简单的boolean类型变量代替。

2 即将或正在处于非运行态的线程停止

        线程的非运行状态常见的有如下两种情况:

可中断等待:线程调用了sleepwait方法,这些方法可抛出InterruptedException

Io阻塞:线程调用了IOread操作或者socketaccept操作,处于阻塞状态。

2.1 处于可中断等待线程的停止

        如果线程调用了可中断等待方法,正处于等待状态,则可以通过调用Threadinterrupt方法让等待方法抛出InterruptedException异常,然后在循环外截获并处理异常,这样便跳出了线程run方法中的循环,以使线程顺利结束。

        上述的stop方法中需要做的修改就是在设置停止变量之后调用interrupt方法:

[java] view plaincopy
  1. private volatile Thread blinker;  
  2. public void stop() {  
  3.         Thread tmp = blinker;  
  4.         blinker = null;  
  5.         if(tmp!=null){  
  6.             tmp.interrupt();  
  7.         }  
  8. }  

         特别的,Thread对象的interrupt方法会设置线程的interruptedFlag,所以我们可以通过判断Thread对象的isInterrupted方法的返回值来判断是否应该继续run方法内的循环,从而代替线程中的volatile停止变量。这时的上述run方法的代码框架就变为如下:

[java] view plaincopy
  1. public void run() {  
  2.         while (!Thread.currentThread().isInterrupted()) {  
  3.             try {  
  4.                 //业务流程  
  5.             } catch (Exception e){}  
  6.         }  
  7. }  

        需要注意的是Thread对象的isInterrupted不会清除interrupted标记,但是Thread对象的interrupted方法(与interrupt方法区别)会清除该标记。

2.2 处于IO阻塞状态线程的停止

         Java中的输入输出流并没有类似于Interrupt的机制,但是JavaInterruptableChanel接口提供了这样的机制,任何实现了InterruptableChanel接口的类的IO阻塞都是可中断的,中断时抛出ClosedByInterruptedException,也是由Thread对象调用Interrupt方法完成中断调用。IO中断后将关闭通道。

        以文件IO为例,构造一个可中断的文件输入流的代码如下:

[java] view plaincopy
  1. new InputStreamReader(  
  2.            Channels.newInputStream(  
  3.                    (new FileInputStream(FileDescriptor.in)).getChannel())));   

         实现InterruptableChanel接口的类包括FileChannel,ServerSocketChannel, SocketChannel, Pipe.SinkChannel andPipe.SourceChannel,也就是说,原则上可以实现文件、Socket、管道的可中断IO阻塞操作。

        虽然解除IO阻塞的方法还可以直接调用IO对象的Close方法,这也会抛出IO异常。但是InterruptableChanel机制能够使处于IO阻塞的线程能够有一个和处于中断等待的线程一致的线程停止方案。

3 处于大数据IO读写中的线程停止

         处于大数据IO读写中的线程实际上处于运行状态,而不是等待或阻塞状态,因此上面的interrupt机制不适用。线程处于IO读写中可以看成是线程运行中的一种特例。停止这样的线程的办法是强行closeio输入输出流对象,使其抛出异常,进而使线程停止。

        最好的建议是将大数据的IO读写操作放在循环中进行,这样可以在每次循环中都有线程停止的时机,这也就将问题转化为如何停止正在运行中的线程的问题了。

4 在线程运行前停止线程

         有时,线程中的run方法需要足够健壮以支持在线程实际运行前终止线程的情况。即在Thread创建后,到Threadstart方法调用前这段时间,调用自定义的stop方法也要奏效。从上述的停止处于等待状态线程的代码示例中,stop方法并不能终止运行前的线程,因为在Threadstart方法被调用前,调用interrupt方法并不会将Thread对象的中断状态置位,这样当run方法执行时,currentThreadisInterrupted方法返回false,线程将继续执行下去。

         为了解决这个问题,不得不自己再额外创建一个volatile标志量,并将其加入run方法的最开头:

[java] view plaincopy
  1. public void run() {  
  2.         if (myThread == null) {  
  3.            return// stopped before started.  
  4.         }  
  5.         while(!Thread.currentThread().isInterrupted()){  
  6.     //业务逻辑  
  7.         }  
  8. }  

         还有一种解决方法,也可以在run中直接使用该自定义标志量,而不使用isInterrupted方法判断线程是否应该停止。这种方法的run代码框架实际上和停止运行时线程的一样。

  • JDK中的Timer和TimerTask详解


目录结构:


Timer和TimerTask
一个Timer调度的例子
如何终止Timer线程
关于cancle方式终止线程
反复执行一个任务
schedule VS. scheduleAtFixedRate
一些注意点
1. Timer和TimerTask


  Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。


  TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。


2. 一个Timer调度的例子


复制代码
 1 import java.util.Timer;
 2 import java.util.TimerTask;
 3 
 4 public class TestTimer {
 5     
 6     public static void main(String args[]){
 7         System.out.println("About to schedule task.");
 8         new Reminder(3);
 9         System.out.println("Task scheduled.");
10     }
11     
12     public static class Reminder{
13         Timer timer;
14         
15         public Reminder(int sec){
16             timer = new Timer();
17             timer.schedule(new TimerTask(){
18                 public void run(){
19                     System.out.println("Time's up!");
20                     timer.cancel();
21                 }
22             }, sec*1000);
23         }
24     } 
25 }
复制代码
运行之后,在console会首先看到:


About to schedule task.
Task scheduled.


然后3秒钟后,看到


Time's up!


从这个例子可以看出一个典型的利用timer执行计划任务的过程如下:


new一个TimerTask的子类,重写run方法来指定具体的任务,在这个例子里,我用匿名内部类的方式来实现了一个TimerTask的子类
new一个Timer类,Timer的构造函数里会起一个单独的线程来执行计划任务。jdk的实现代码如下:
复制代码
1     public Timer() {
2         this("Timer-" + serialNumber());
3     }

5     public Timer(String name) {
6         thread.setName(name);
7         thread.start();
8     }
复制代码
调用相关调度方法执行计划。这个例子调用的是schedule方法。
任务完成,结束线程。这个例子是调用cancel方法结束线程。
3. 如何终止Timer线程


  默认情况下,创建的timer线程会一直执行,主要有下面四种方式来终止timer线程:


调用timer的cancle方法
把timer线程设置成daemon线程,(new Timer(true)创建daemon线程),在jvm里,如果所有用户线程结束,那么守护线程也会被终止,不过这种方法一般不用。
当所有任务执行结束后,删除对应timer对象的引用,线程也会被终止。
调用System.exit方法终止程序
4. 关于cancle方式终止线程


这种方式终止timer线程,jdk的实现比较巧妙,稍微说一下。


首先看cancle方法的源码:


复制代码
1     public void cancel() {
2         synchronized(queue) {
3             thread.newTasksMayBeScheduled = false;
4             queue.clear();
5             queue.notify();  // In case queue was already empty.
6         }
7     }
复制代码
没有显式的线程stop方法,而是调用了queue的clear方法和queue的notify方法,clear是个自定义方法,notify是Objec自带的方法,很明显是去唤醒wait方法的。


再看clear方法:


复制代码
1     void clear() {
2         // Null out task references to prevent memory leak
3         for (int i=1; i<=size; i++)
4             queue[i] = null;

6         size = 0;
7     }
复制代码
clear方法很简单,就是去清空queue,queue是一个TimerTask的数组,然后把queue的size重置成0,变成empty.还是没有看到显式的停止线程方法,回到最开始new Timer的时候,看看new Timer代码:


复制代码
1     public Timer() {
2         this("Timer-" + serialNumber());
3     }

5     public Timer(String name) {
6         thread.setName(name);
7         thread.start();
8     }
复制代码
看看这个内部变量thread:


1     /**
2      * The timer thread.
3      */
4     private TimerThread thread = new TimerThread(queue);
不是原生的Thread,是自定义的类TimerThread.这个类实现了Thread类,重写了run方法,如下:


复制代码
 1     public void run() {
 2         try {
 3             mainLoop();
 4         } finally {
 5             // Someone killed this Thread, behave as if Timer cancelled
 6             synchronized(queue) {
 7                 newTasksMayBeScheduled = false;
 8                 queue.clear();  // Eliminate obsolete references
 9             }
10         }
11     }
复制代码
最后是这个mainLoop方法,这方法比较长,截取开头一段:


复制代码
 1     private void mainLoop() {
 2         while (true) {
 3             try {
 4                 TimerTask task;
 5                 boolean taskFired;
 6                 synchronized(queue) {
 7                     // Wait for queue to become non-empty
 8                     while (queue.isEmpty() && newTasksMayBeScheduled)
 9                         queue.wait();
10                     if (queue.isEmpty())
11                         break; // Queue is empty and will forever remain; die
复制代码
可以看到wait方法,之前的notify就是通知到这个wait,然后clear方法在notify之前做了清空数组的操作,所以会break,线程执行结束,退出。


5. 反复执行一个任务


通过调用三个参数的schedule方法实现,最后一个参数是执行间隔,单位毫秒。


6. schedule VS. scheduleAtFixedRate


这两个方法都是任务调度方法,他们之间区别是,schedule会保证任务的间隔是按照定义的period参数严格执行的,如果某一次调度时间比较长,那么后面的时间会顺延,保证调度间隔都是period,而scheduleAtFixedRate是严格按照调度时间来的,如果某次调度时间太长了,那么会通过缩短间隔的方式保证下一次调度在预定时间执行。举个栗子:你每个3秒调度一次,那么正常就是0,3,6,9s这样的时间,如果第二次调度花了2s的时间,如果是schedule,就会变成0,3+2,8,11这样的时间,保证间隔,而scheduleAtFixedRate就会变成0,3+2,6,9,压缩间隔,保证调度时间。


7. 一些注意点


每一个Timer仅对应唯一一个线程。
Timer不保证任务执行的十分精确。
Timer类的线程安全的。

0 0