Java线程的中断

来源:互联网 发布:mac 邮件 编辑:程序博客网 时间:2024/05/16 01:17

  之前一直对Java的中断机制没有想的很清楚,就打算写这篇文章来讲解自己对Java中断机制的理解,并附上了相应的验证代码。

中断是一种协作机制

  必须记住,中断是一种协作机制,Java里的中断都是依赖于线程的当前中断标志信号的状态来判断的。当在一个线程里改变另一个线程的中断标志信号状态时,被中断的线程不一定要立即停止正在做的事情。相反,中断是礼貌地请求另一个线程在它愿意并且方便的时候停止它正在做的事情。只有run方法结束,那这个线程就结束了。


Java里与中断相关的方法

  Java中断一个线程主要使用了三个方法:

方法签名 注意事项 方法说明 public static boolean interrupted() 这是一个静态方法,一般使用Thread.interrupted()方式调用 测试当前线程是否已经中断。线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。 public boolean isInterrupted() 由线程类的实例对象调用 测试线程是否已经中断。线程的中断状态不受该方法的影响。 public void interrupt() 由线程类的实例对象调用 中断线程,本质上是改变了调用者代表的线程的中断标志信号。

  三个方法中,interrupt()方法是唯一能将中断状态(中断标志信号)设置为true的方法,另外两个方法都是用于检测当前中断状态的方法。需要注意的是,这三个方法都不会抛出InterruptedException异常。一般来说,中断主要有两种情形,下面分别来说这两种情形。


中断非阻塞状态下的线程

  我们假设只有主线程main和副线程viceThread两个线程在工作,目标是中断viceThread,此时viceThread没有被阻塞,那么为了中断viceThread,我们需要在main线程里改变viceThread的中断信号状态,使用viceThread调用interrupte()方法,然后在viceThread线程里判断viceThread线程是否收到了中断的信号,也就是interrupted()/isInterrupted()方法检测。下面是代码。

//副线程类public class MyThread extends Thread {    public void run() {        for (int i = 0; i < 1000; i++) {            if (Thread.interrupted())// 检测线程的中断状态            {                // 线程收到了中断信号的处置                System.out.println("Someone interrupted me.");                break;            }             else             {                // 线程未收到中断信号的处置                System.out.println("Executing..." + i);            }        }    }}
/** * 演示非阻塞中的线程被中断的情况 */public class NonBlockThreadInterrupt {    public static void main(String[] args) {        MyThread viceThread = new MyThread();        viceThread.start();        for(int i = 0; i < 1000; i++){            if(i == 500){                System.out.println("main主线程给viceThread副线程一个中断信号");                viceThread.interrupt();            }            System.out.println("main主线程...." + i);        }    }}

  这段代码里,main主线程和一个viceThread副线程在执行,为了中断副线程,需要在主线程里修改副线程的中断信号状态,也就是”viceThread.interrupt();”这句,然后在MyThread类里的run()方法里有判断该类中断状态的语句,这样就可以达到中断线程的目的了。运行结果如下:

//...//main主线程....498//main主线程....499//Executing...991//main主线程给viceThread副线程一个中断信号//Executing...992//Executing...993//Executing...994//Executing...995//main主线程....500//...//Someone interrupted me.//main主线程....514//main主线程....515//...//main主线程....999

  因为输出结果很多,所以我只放了关键部分在上面。我们来分析一下这个结果,在输出“main主线程给viceThread副线程一个中断信号”前,副线程和主线程交替执行,然后main主线程里更改副线程的中断状态,更改结束后副线程还执行了四次,但没有到999,也就是说main主线程执行到了“System.out.println(“main主线程给viceThread副线程一个中断信号”);”这句话,还未来得及执行“viceThread.interrupt();”就让出了CPU执行权,此后,main线程再次获取到CPU执行权,并执行“viceThread.interrupt();”更改了副线程的中断状态,输出“Someone interrupted me.”后,不再出现副线程else部分的执行,表明此时副线程已经被中断了,这也正好验证了当在一个线程里改变另一个线程的中断标志信号状态时,被中断的线程不一定要立即停止正在做的事情,否则的话,输出“main主线程给viceThread副线程一个中断信号”后就不会出现副线程还在执行的情况。


中断阻塞状态下的线程

  关于阻塞状态下的线程的中断,这里只讨论会抛出InterruptedException异常的中断情况。我们首先要搞清楚的是InterruptedException出现的条件以及哪些方法会抛出这个异常。

1. InterruptedException异常

  InterruptedException异常类会在这种条件下出现:当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常。有时候,一种方法可能希望测试当前线程是否已被中断,如果已被中断,则立即抛出此异常。下列代码可以达到这种效果:

if (Thread.interrupted())  // Clears interrupted status!   throw new InterruptedException();

  注意了,这个异常出现在试图中断一个阻塞状态中的线程时,所以,我们可以利用抛出的这个异常来中断这个阻塞中的线程。

2. 哪些常用方法会抛出InterruptedException异常

  需要搞清楚的一点是:interrupt()方法不会抛出InterruptedException异常!InterruptedException异常是由一些引起阻塞的方法抛出的!!并且,这些方法抛出异常后会重新把中断标志修改为true!!!

方法 说明 public final void join() 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态被清除。 public static void sleep() 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态被清除。 public final void wait() 如果在当前线程等待通知之前或者正在等待通知时,任何线程中断了当前线程。在抛出此异常时,当前线程的中断状态被清除。

3. 验证代码

public class BlockThread extends Thread {    private Thread parent;    private boolean flag = true;    public BlockThread(Thread parent){        this.parent = parent;    }    public void run(){        while(true){            if(flag){                System.out.println("sub thread is running...");                parent.interrupt();//main线程在这里改变了中断标志,试图中断阻塞中的主线程            }else{                System.out.println("flag标志改变,结束副线程");                break;            }        }    }    public void changeFlag(){        this.flag = false;    }    public static void main(String[] args){          BlockThread t = new BlockThread(Thread.currentThread());        t.start();        try {            t.join();//这里阻塞了main线程,所以抛出了InterruptedException        } catch (InterruptedException e) {            System.out.println("Parent thread will die...");            t.changeFlag();        }    } }//运行结果://sub thread is running...//sub thread is running...//sub thread is running...//sub thread is running...//sub thread is running...//sub thread is running...//sub thread is running...//sub thread is running...//Parent thread will die...//sub thread is running...//flag标志改变,结束副线程

  上面的代码使用了一个main主线程和另外一个线程t,通过t.join()将main线程阻塞,然后在t线程里更改main线程的中断标志,此时由于main线程已被阻塞,所以t.join()抛出一个InterruptedException异常,然后输出”Parent thread will die…”后将flag的值改为false,t线程继续执行,但是此时进入else部分,将t线程也结束了。注意到处理异常时执行完了System.out.println(“Parent thread will die…”)后t.changeFlag()还未执行就被t线程抢走了CPU执行权,因此又输出了一句“sub thread is running…”。
  如果else里面没有加”break;’语句,此时t线程会一直执行else部分。其实,中断阻塞状态下的线程,就是将该线程恢复到了可运行状态,并不是将这个线程杀死。
  这个例子有点绕,但是很能说明试图中断一个阻塞中的线程时,会由谁抛出什么异常,我们应该在何处怎么处理这个异常。其余的两种方法在使用时,先想清楚这三个问题,就很好使用了,由于这三个方法调用后会清除中断状态,所以往往我们在catch里调用interrupt()方法使要中断的线程的中断状态更改为true。

  关于其他的阻塞情况,有这样的结论:

  1. 如果是java.nio.channels.InterruptibleChannel进行的io操作引起的阻塞,则会对线程抛出一个ClosedByInterruptedException;
  2. 如果是轮询(java.nio.channels.Selectors)引起的线程阻塞,则立即返回,不会抛出异常。

中断的使用

  通常,中断的使用场景有以下几个:

  1. 点击某个桌面应用中的取消按钮时;
  2. 某个操作超过了一定的执行时间限制需要中止时;
  3. 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
  4. 一组线程中的一个或多个出现错误导致整组都无法继续时;
  5. 当一个应用或服务需要停止时。

  关于Java的中断机制,暂时就写到这里,以后有新体会再增加吧~~

原创粉丝点击