多线程 - 如何正确的终止线程?interrupt()、interrupted()、isInterrupted()区别?
总结
java线程之间是协同式,不是抢占式
因为是协同式,所以线程之间都是“商量”着来,最佳实践是没有谁强迫谁终止的情况的,因此才会淘汰stop()方法,详见 多线程 - 为何要弃用stop() suspend()?(不过你硬要调用stop()就另当别论...)
- public void interrupt() 会为指定线程设置一个interrupted状态 -- 和stop()不同,不会强制线程停止,只会设置一个“interrupt标志位”,置为true(连续调用还是true,不会有异常发生。对比 Thread.start(), 多次调用会出现java.lang.IllegalStateException)。代表着“该线程被建议终止”。但是是否理会该建议,完全看该线程的具体逻辑是否去检查这个标志位,然后根据标志做出不同的反应。也有可能该线程的业务逻辑完全不理会标志位的变化,也就会继续我行我素的运行。
- public static boolean interrupted() 会判断当前线程的interrupted状态,并且会重置该状态 -- 检查是否当前线程被设置了“interrupt标志位”。如果被设置了,返回true,反之返回false。之后,该标志位会被清除。
- public boolean isInterrupted() 会判断指定线程的interrupted状态 -- 检查是否当前线程被设置了“interrupt标志位”。如果被设置了,返回true,反之返回false。但是,该函数不会清除标志位。
@Test public void testInterrupted(){ boolean b1 = Thread.interrupted(); //false Thread.currentThread().interrupt(); boolean b2 = Thread.interrupted(); //true boolean b3 = Thread.interrupted(); //false } @Test public void testIsInterrupted(){ boolean b4 = Thread.currentThread().isInterrupted(); //false Thread.currentThread().interrupt(); boolean b5 = Thread.currentThread().isInterrupted(); //true boolean b6 = Thread.currentThread().isInterrupted(); //true }
调用interrupt()抛出异常的情况(必看)
对于interrupt()方法,根据官方文档:
- 如果指定线程调用了interrupt(),并且该线程不处于被sleep,wait,join的状态,那么一切正常,不会抛出调用InterruptException异常,就不会清除中断标志位。如果此时再调用isInterrupted()就会返回true。
- 如果指定线程调用了interrupt()时,而指定线程正处于阻塞状态sleep,wait,join...线程自己就会立即抛InterruptException异常。
- 原本“interrupt标志位”应该是true(因为之前调用了interrupt()),也会被清除变为false。如果再调用isInterrupted()返回false。
- 如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态;如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。
- 需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。
Q:为什么当指定线程在被阻塞状态(sleep, wait)时,调用interrupt(),要立即抛出InterruptException异常,并清除“interrupt标志位”(从true变为false)
A:【本答案不是很严谨,请大家斧正】线程在被阻塞过程中,可能掌握着某种资源。如果线程刚从阻塞回来,“interrupt标志位”就是true,很可能(视用户自定义的代码而定)线程就真准备中断,没有给程序任何回收资源的时间。因此先取消标志位,回收下资源然后视情况再决定是否真的interrupt。
如果中断sleep,wait,join等,就会抛InterruptException异常,就会清除中断标志位,那么这种情况应该怎么处理呢?为了保证数据的一致性和完整性,我们需要用Thread.interrupt()方法再次中断自己,置上中断标志位。例子如下:
@Test public void testInterrupt() throws InterruptedException { Thread t1 = new Thread() { public void run() { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("Interruted!"); break; } try { Thread.sleep(2000); // 睡眠时中断会清除中断标志位 } catch (InterruptedException e) { // 如果少了下面这句,这个线程虽然在外面中断,但是只要中断睡眠中的进程 // 就会清除中断标志位,仍然处于无限循环,会竞争CPU资源 Thread.currentThread().interrupt(); // 再次中断置上中断标记 } Thread.yield(); } } }; t1.start(); //t1 thread would running Thread.sleep(200);//sleep main thread t1.interrupt(); //after main thread wake up, would plan to interrupt t1 thread }
interrupt()官方文档
中断线程。 如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。 如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。 如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。 如果以前的条件都没有保存,则该线程的中断状态将被设置。 中断一个不处于活动状态的线程不需要任何作用。 抛出: SecurityException - 如果当前线程无法修改该线程