线程的中断
-
java提供了中断机制,我们可以使用它来结束一个线程。这种机制要求线程检查它是否被中断了。然后决定是不是响应这个中断请求,线程允许忽略中端请求并且继续执行。Java的中断是一种协作机制,也就是说调用线程对象的
interrupt()
并一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己。每个线程都有一个boolean的中断状态(不一定就是对象的属性,事实上,该状态也确实不是Thread的字段),interrupt()
仅仅只是将该状态置为true。当对一个线程,调用interrupt()
时:- 对于处于非阻塞状态的线程, 只是改变了中断状态, 即
Thread.isInterrupted()
将返回true,被设置中断标志的线程将继续正常运行,不受任何影响。 - 对于处于被阻塞状态的线程(例如处于
sleep
,wait
,join
等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException
异常,仅此而已。 - 所以调用线程的
interrupt()
并不能真正的中断线程,需要被调用的线程自己进行处理才行。
- 对于处于非阻塞状态的线程, 只是改变了中断状态, 即
-
一个多个线程在执行的Java程序,只有当其全部的线程执行结束时(更具体的说,是所有非守护线程结束或者某个线程调用System.exit()方法的时候),它才会结束运行。与线程中断有关的,有三个方法
public void Thread.interrupt();
//中断线程,Thread.interrupt()是实例方法,它通知目标线程中断,也就是设置中断标志,中断标志表示当前线程已经被中断了。public boolean Thread.isInterrupted();
//判断线程是否被中断,Thread.isInterrupted()方法也是实例方法,判断当前线程是否有被中断(通过检查中断标志位)public static boolean Thread.interrupted();
//判断是否被中断,并清除当前中断状态。Thread.interrupted()判断当前线程的中断状态,但同时会清除当前线程的中断标志位状态。
-
一个线程如果有被中断的需求
- 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
- 在调用阻塞方法时正确处理
InterruptedException
异常。(例如,catch异常后就结束线程。)
-
Thread.interrupt()
方法和InterruptedException
异常的关系?是由interrupt()
触发产生了InterruptedException
异常?Thread.interrupt()
只是在Object.wait()
.Object.join()
,Object.sleep()
几个方法会主动抛出InterruptedException
异常。而在其他的情况,只是通过设置了Thread的一个标志位信息,需要程序自我进行处理。
-
java标准库很多阻塞方法对中断的响应方式都是抛出
InterruptedException
异常。凡是抛出中断异常的方法,通常在其抛出该异常之前将当前的线程中断标记重置为false。方法 为响应中断而抛出的异常 Object.wait()/wait(long) InterruptedException Thread.sleep(long) InterruptedException Thread.join()/join(long) InterruptedException java.util.concurrent.BlockingQueue.take()/put(E) InterruptedException Lock.lockInterruptibly() InterruptedException CountDownLatch.await()/CyclicBarrier.await() InterruptedException -
目标线程可能因为执行
CountDownLatch.await()
、CyclicBarrier.await()
等能够响应中断的阻塞方法而被暂停时,发起线程给这些方法的执行线程发送中断会导致jvm将相应的线程唤醒并使其抛出InterruptedException
,即给目标线程发送中断还能够产生唤醒目标线程的效果。
案例
-
线程任务通过判断中断状态,自行处理任务继续还是退出,任务非阻塞状态
/** * @author jannal * 质数生成器 **/ public class PrimeNumberGenerator extends Thread { @Override public void run() { Thread.currentThread().setName("PN-Thread"); long number = 1L; while (true) { /** * 如果将此处的代码去掉,则当执行task.interrupt(); 时,此时线程被设置为中断状态, * 但是这个中断不会产生任何作用,仅仅是当前线程的中断标志位状态变为中断状态(设置为true), * 其他的没有任何影响,线程继续执行 */ if (isInterrupted()) { String threadName = Thread.currentThread().getName(); System.out.println(String.format("%s线程被中断,中断状态:%s", threadName, Thread.interrupted())); return; } if (isPrime(number)) { System.out.println(String.format("%d是素数", number)); } number++; } } private boolean isPrime(long number) { if (number <= 2) { return true; } for (long i = 2; i < number; i++) { if ((number % i) == 0) { return false; } } return true; } } 输出结果: 1是素数 2是素数 ...省略... 14633是素数 14639是素数 PN-Thread线程被中断,中断状态:true
-
任务阻塞状态下的中断异常处理。
Thread.sleep()
会让当前线程休眠若干时间,它会抛出一个InterruptedException
中断异常。InterruptedException
不是运行时异常,也就是说程序必须捕获并且处理它。当线程在sleep休眠时,如果被中断,这个异常就会发生。@Test public void testBlockInterrupt() { Thread t = new Thread() { @Override public void run() { Thread.currentThread().setName("BK-Thread"); while (true) { String threadName = Thread.currentThread().getName(); if (Thread.currentThread().isInterrupted()) { System.out.println(String.format("%s已经被设置为中断状态,此时退出", threadName)); return; } try { /** * 如果在此处程序被中断,则程序会抛出InterruptedException * 在catch子句中由于已经捕获到了中断,我们可以立即退出线程,但是 * 这样后续的代码无法执行,所以我们为了保证后续代码可以执行,在捕获 * 之后执行Thread.currentThread().interrupt();设置中断标志,在任务的执行入口 * 判断线程中断状态。 */ Thread.sleep(2000); } catch (InterruptedException e) { System.out.println(String.format("%s的sleep被中断,当前中断状态:%s", threadName,Thread.currentThread().isInterrupted())); //设置中断状态,抛出异常后会清除中断标记位 Thread.currentThread().interrupt(); } Thread.yield(); } } }; t.start(); try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt(); LockSupport.park(); } 输出: BK-Thread的sleep被中断,当前中断状态:false BK-Thread已经被设置为中断状态,此时退出
-
如果线程实现了复杂的算法并且分布在几个方法中,获取线程里有递归调用的方法,我们就得使用一个更好的机制来控制线程的中断。为了达到这个目的,java提供了
InterruptedException
异常。当检查到线程中断的时候,就抛出这个异常,然后再run()方法中捕获并处理这个异常public class FileSearch implements Runnable { private String initPath; private String fileName; public FileSearch(String initPath, String fileName) { super(); this.initPath = initPath; this.fileName = fileName; Thread.currentThread().setName("FS-Thread"); } @Override public void run() { File file = new File(initPath); if (file.isDirectory()) { try { directoryProcess(file); } catch (InterruptedException e) { String threadName = Thread.currentThread().getName(); System.out.println(String.format("搜索线程%s被中断", threadName)); Thread.currentThread().interrupt(); return; } } } //目录处理 private void directoryProcess(File file) throws InterruptedException { File[] listFiles = file.listFiles(); if (listFiles != null) { for (int i = 0; i < listFiles.length; i++) { if (listFiles[i].isDirectory()) { directoryProcess(listFiles[i]); } else { fileProcess(listFiles[i]); } } if (Thread.interrupted()) { throw new InterruptedException(); } } } //文件处理; private void fileProcess(File file) throws InterruptedException { if (file.getName().equals(fileName)) { String threadName = Thread.currentThread().getName(); System.out.println(String.format("线程名称:%s,文件路径:%s", threadName, file.getAbsolutePath())); } if (Thread.interrupted()) { throw new InterruptedException(); } } } @Test public void testFileSeacrh() { String path = "/Volumes/N/"; FileSearch fileSearch = new FileSearch(path, "a.txt"); Thread thread = new Thread(fileSearch); thread.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } thread.interrupt(); LockSupport.park(); } 输出: 搜索线程Thread-0被中断,退出任务