多线程中级篇(2) 线程中断
条评论我们在基础篇的线程介绍里面已经提到过如何中断线程,本文将详细展开,描述其中的细节,让大家对线程中断有一个更深刻的理解。
线程中断
停止线程
虽然在Thread中提供了stop()方法,但是已经声明为@Deprecated不再使用了。所以,其实Java并没有给我们提供一种简单和方便的优雅停止线程的方法。
|
Java没有提供,那只能我们自己来实现了,比较容易想到的是加一个标志位stop=false,在线程执行过程中不断判断这个标志位,当标志为被置为true时,正常退出线程。
注意,这个标志位是典型的volatile关键字的应用场景:所有线程共享这个标志位,每个线程在自己的工作内存中有一个备份,当外部修改主内存中的标志位后,各个线程需要立即读取到变化,完成线程退出。
public class Task implements Runnable { |
Task task = new Task(); |
中断线程
以上代码可以实现线程的正常停止,没有问题。但是有一种情况无法处理,那就是如果在while()语句块出现了I/O或者同步相关的阻塞操作,那么就无法判断stop的值了,上面停止线程的方法就失效了。怎么办呢?这个时候就需要中断线程了。
首先,这些阻塞方法是可以被中断的,一旦被中断将抛出InterruptedException异常;从下面的方法声明中可以看到,join()、sleep()和wait()都声明抛出InterruptedException异常,也就是说这些阻塞方法会检查线程的中断状态,如果发现被中断就抛出异常,变相提供了一种退出机制。
public class Thread { |
public class Object { |
Thread类提供了方法来查询和设置中断状态。
public class Thread { |
当调用了一个线程的interrupt()方法后,当前线程的状态被置为中断状态,但是线程不会自动停止,需要我们来进行处理,停止线程的代码可以修改为:
public class MyThread extends Thread { |
MyThread thread = new MyThread(); |
这里将while(!stop)修改为while (!isInterrupted()),停止方法由调用自定义的stop()方法改为调用Thread类实例对象的interrupt()方法。
实战中,我们不会在线程执行过程中每执行一步就判断一次isInterrupted(),所以更加贴近现实场景的情况是中断阻塞操作。前面讲过,这时会抛出异常,我们捕获异常并退出程序即可。
public class MyThread extends Thread { |
我们以sleep()阻塞为例,wait()和join()也是一样的;当外部调用myThread.interrupt()时,发现MyThread正处于sleep的阻塞状态,会让sleep()抛出InterruptedException异常,我们捕获异常退出即可。
注意,如果外部调用interrupt()方法时,线程处于非阻塞状态,那么状态被设置为interrupted;如果线程处于阻塞状态,那么抛出异常,但是线程状态不会被修改。所以,我们可以在捕获异常后自行设置中断状态,统一退出,看上去是更优雅的做法,代码如下。
public class MyThread extends Thread { |
Thread.currentThread().interrupt()手工设置中断状态,然后通过isInterrupted()判断退出。
实战
原来停止或者中断一个线程如此麻烦,那实战中如何使用呢?
其实,实战反而简单。因为实战中我们通常只实现Runnable接口,并把任务提交给Executor去处理。虽然我们自己没有创建Thread对象,但最后线程池里面保存的肯定是Thread对象,也就是说Executor帮我们创建了Thread对象,它实现了一种中断机制来保证任务可以被中断或取消。
下面通过例子代码看看如何使用。
TODO FutureTask的取消