Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。
每个线程对象里都有一个boolean类型的标识, 代表着是否有中断请求。当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。
public static boolean interrupted() 该方法是用于清除线程的中断状态, 当运行两次该方法的时候, 第二次返回的是false!
public boolean isInterrupted() 判断线程是否中断了, 它不影响线程的状态!
public void interrupt() 中断线程
Java中断机制只是设置被中断线程的中断状态
处理时机
作为一种协作机制,不会强求被中断线程一定要在某个点进行处理。处理时机决定着程序的效率与中断响应的灵敏性。频繁的检查中断状态可能会使程序执行效率下降,相反,检查的较少可能使中断请求得不到及时响应。如果发出中断请求之后,被中断的线程继续执行一段时间不会给系统带来灾难,那么就可以将中断处理放到方便检查中断,同时又能从一定程度上保证响应灵敏度的地方。当程序的性能指标比较关键时,可能需要建立一个测试模型来分析最佳的中断检测点,以平衡性能和响应灵敏性。
处理方式
1、 中断状态的管理
一般说来,当可能阻塞的方法声明中有抛出InterruptedException则暗示该方法是可中断的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep等,如果程序捕获到这些可中断的阻塞方法抛出的InterruptedException或检测到中断后, 一般有以下两个通用原则来处理中断信息:
- 如果遇到的是可中断的阻塞方法抛出InterruptedException,可以继续向方法调用栈的上层抛出该异常,如果是检测到中断,则可清除中断状态并抛出InterruptedException,使当前方法也成为一个可中断的方法。
- 若有时候不太方便在方法上抛出InterruptedException,比如要实现的某个接口中的方法签名上没有throws InterruptedException,这时就可以捕获可中断方法的InterruptedException并通过Thread.currentThread.interrupt()来重新设置中断状态。如果是检测并清除了中断状态,亦是如此。
一般的代码中,尤其是作为一个基础类库时,绝不应当吞掉中断,即捕获到InterruptedException后在catch里什么也不做,清除中断状态后又不重设中断状态也不抛出InterruptedException等。因为吞掉中断状态会导致方法调用栈的上层得不到这些信息。
总得来说,就是要让方法调用栈的上层获知中断的发生。
2、 中断的响应
程序里发现中断后该怎么响应?这就得视实际情况而定了。有些程序可能一检测到中断就立马将线程终止,有些可能是退出当前执行的任务,继续执行下一个任务……作为一种协作机制,这要与中断方协商好,当调用interrupt会发生些什么都是事先知道的,如做一些事务回滚操作,一些清理工作,一些补偿操作等。若不确定调用某个线程的interrupt后该线程会做出什么样的响应,那就不应当中断该线程。
Thread.interrupt VS Thread.stop的区别
1)Thread.stop与中断机制有着相似之处, 如当线程在等待内置锁或IO时,stop跟interrupt一样,不会中止这些操作;当catch住stop导致的异常时,程序也可以继续执行,虽然stop本意是要停止线程,这么做会让程序行为变得更加混乱
2)最重要的就是中断需要程序自己去检测然后做相应的处理,而Thread.stop会直接在代码执行过程中抛出ThreadDeath错误,这是一个java.lang.Error的子类。
Demo:
package com.ticmy.interrupt; import java.util.Arrays; import java.util.Random; import java.util.concurrent.TimeUnit; public class TestStop { private static final int[] array = new int[80000]; private static final Thread t = new Thread() { public void run() { try { System.out.println(sort(array)); } catch (Error err) { err.printStackTrace(); } System.out.println("in thread t"); } }; static { Random random = new Random(); for(int i = 0; i < array.length; i++) { array[i] = random.nextInt(i + 1); } } private static int sort(int[] array) { for (int i = 0; i < array.length-1; i++){ for(int j = 0 ;j < array.length - i - 1; j++){ if(array[j] < array[j + 1]){ int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } return array[0]; } public static void main(String[] args) throws Exception { t.start(); TimeUnit.SECONDS.sleep(1); System.out.println("go to stop thread t"); t.stop(); System.out.println("finish main"); } }
结果:
go to stop thread t java.lang.ThreadDeath at java.lang.Thread.stop(Thread.java:758) at com.ticmy.interrupt.TestStop.main(TestStop.java:44) finish main in thread t
中断的使用
通常,中断的使用场景有以下几个:
- 点击某个桌面应用中的取消按钮时;
- 某个操作超过了一定的执行时间限制需要中止时;
- 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
- 一组线程中的一个或多个出现错误导致整组都无法继续时;
- 当一个应用或服务需要停止时。
Demo:
这里新建了一个磁盘文件扫描的任务,扫描某个目录下的所有文件并将文件路径打印到控制台,
扫描的过程可能会很长。若需要中止该任务,只需在控制台键入quit并回车即可。
import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; public class FileScanner { private static void listFile(File f) throws InterruptedException { if(f == null) { throw new IllegalArgumentException(); } if(f.isFile()) { System.out.println(f); return; } File[] allFiles = f.listFiles(); if(Thread.interrupted()) { throw new InterruptedException("文件扫描任务被中断"); } for(File file : allFiles) { //还可以将中断检测放到这里 listFile(file); } } public static String readFromConsole() { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); try { return reader.readLine(); } catch (Exception e) { e.printStackTrace(); return ""; } } public static void main(String[] args) throws Exception { final Thread fileIteratorThread = new Thread() { public void run() { try { listFile(new File("c:\\")); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread() { public void run() { while(true) { if("quit".equalsIgnoreCase(readFromConsole())) { if(fileIteratorThread.isAlive()) { fileIteratorThread.interrupt(); return; } } else { System.out.println("输入quit退出文件扫描"); } } } }.start(); fileIteratorThread.start(); } }
在扫描文件的过程中,对于中断的检测这里采用的策略是,如果碰到的是文件就不检测中断,是目录才检测中断,因为文件可能是非常多的,每次遇到文件都检测一次会降低程序执行效率。此外,在fileIteratorThread线程中,仅是捕获了InterruptedException,没有重设中断状态也没有继续抛出异常,因为我非常清楚它的使用环境,run方法的调用栈上层已经没有可能需要检测中断状态的方法了。
|
在这个程序中,输入quit完全可以执行System.exit(0)操作来退出程序,但正如前面提到的,这是个GUI程序核心逻辑的模拟,
在GUI中,执行System.exit(0)会使得整个程序退出。