一、概述
之前讲解Thread类中方法的时候,interrupt()、interrupted()、isInterrupted()三个方法没有讲得很清楚,只是提了一下。现在把这三个方法同一放到这里来讲,因为这三个方法都涉及到多线程的一个知识点----中断机制。
Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。有个例子举个蛮好,就像父母叮嘱出门在外的子女要注意身体一样,父母说了,但是子女是否注意身体、如何注意身体,还是要看自己。
中断机制也是一样的,每个线程对象里都有一个标识位表示是否有中断请求(当然JDK的源码是看不到这个标识位的,是虚拟机线程实现层面的),代表着是否有中断请求。
二、三个中断有关的方法
1、interrupt()方法
1 1 /** 2 2 * Interrupts this thread. 3 3 * 4 4 * <p> Unless the current thread is interrupting itself, which is 5 5 * always permitted, the {@link #checkAccess() checkAccess} method 6 6 * of this thread is invoked, which may cause a {@link 7 7 * SecurityException} to be thrown. 8 8 * 9 9 * <p> If this thread is blocked in an invocation of the {@link 1010 * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link 1111 * Object#wait(long, int) wait(long, int)} methods of the {@link Object} 1212 * class, or of the {@link #join()}, {@link #join(long)}, {@link 1313 * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, 1414 * methods of this class, then its interrupt status will be cleared and it 1515 * will receive an {@link InterruptedException}. 1616 * 1717 * <p> If this thread is blocked in an I/O operation upon an {@link 1818 * java.nio.channels.InterruptibleChannel InterruptibleChannel} 1919 * then the channel will be closed, the thread's interrupt 2020 * status will be set, and the thread will receive a {@link 2121 * java.nio.channels.ClosedByInterruptException}. 2222 * 2323 * <p> If this thread is blocked in a {@link java.nio.channels.Selector} 2424 * then the thread's interrupt status will be set and it will return 2525 * immediately from the selection operation, possibly with a non-zero 2626 * value, just as if the selector's {@link 2727 * java.nio.channels.Selector#wakeup wakeup} method were invoked. 2828 * 2929 * <p> If none of the previous conditions hold then this thread's interrupt 3030 * status will be set. </p> 3131 * 3232 * <p> Interrupting a thread that is not alive need not have any effect. 3333 * 3434 * @throws SecurityException 3535 * if the current thread cannot modify this thread 3636 * 3737 * @revised 6.0 3838 * @spec JSR-51 3939 */ 4040 public void interrupt() { 4141 if (this != Thread.currentThread()) 4242 checkAccess(); 4343 4444 synchronized (blockerLock) { 4545 Interruptible b = blocker; 4646 if (b != null) { 4747 interrupt0(); // Just to set the interrupt flag 4848 b.interrupt(this); 4949 return; 5050 } 5151 } 5252 interrupt0(); 5353 } 54 551 /* Some private helper methods */ 562 private native void setPriority0(int newPriority); 573 private native void stop0(Object o); 584 private native void suspend0(); 595 private native void resume0(); 606 private native void interrupt0(); 617 private native void setNativeName(String name);
①:看一下第47行,interrupt()方法的作用只是设置中断标识位(Just to set the interrupt flag),并不会强制要求线程必须进行处理。再看一下第32行,interrupt()方法作用的线程是处于不是终止状态或新建状态的线程(Interrupting a thread that is not alive need not have any effect.)。
关于线程是否是alive的判断(A thread is alive if it has been started and has not yet died.)
②:看一下第29-30行,只有当那三种情况都不成立时,interrupt()方法才会设置线程的中断标识位(If none of the previous conditions hold then this thread's interrupt status will be set.)。这里介绍第一种,当调用Object的wait()/wait(long)/wait(long, int)方法,或是调用线程的join()/join(long)/join(long, int)/sleep(long)/sleep(long, int)方法, 那么interrupt()方法作用的线程的中断标识位会被清除并抛出InterruptedException异常。其余两种情况自己参看注释。
③:看一下第二块代码的第6行interrupt0()方法(设置中断标识位),这是一个被native修饰的方法,很明显这是一个本地方法,是Java虚拟机实现的。
2、isInterrupted()
该方法的作用就是测试线程是否已经中断,且线程的中断标识位并不受该方法的影响。
1 1 /** 2 2 * Tests whether this thread has been interrupted. The <i>interrupted 3 3 * status</i> of the thread is unaffected by this method. 4 4 * 5 5 * <p>A thread interruption ignored because a thread was not alive 6 6 * at the time of the interrupt will be reflected by this method 7 7 * returning false. 8 8 * 9 9 * @return <code>true</code> if this thread has been interrupted; 1010 * <code>false</code> otherwise. 1111 * @see #interrupted() 1212 * @revised 6.0 1313 */ 1414 public boolean isInterrupted() { 1515 return isInterrupted(false); 1616 } 1717 1818 /** 1919 * Tests if some Thread has been interrupted. The interrupted state 2020 * is reset or not based on the value of ClearInterrupted that is 2121 * passed. 2222 */ 2323 private native boolean isInterrupted(boolean ClearInterrupted);
①:注意一下第5-7行,若是调用isInterrupted()方法时,当前已经调用interrupt()方法的线程was not alive,方法会返回false,而不是true。
②:最终调用的是isInterrupted(boolean ClearInterrupted),这个方法是一个native的,看得出也是Java虚拟机实现的。方法的参数ClearInterrupted,顾名思义,清除中断标识位,这里传递false,明显就是不清除。
举例:验证一下①中情况
1public class Thread01 extends Thread{ 2 3 @Override 4 public void run() { 5 6 } 7}
测试一下:
1public class Test { 2 public static void main(String[] args) throws InterruptedException { 3 Thread01 thread01 = new Thread01(); 4 thread01.start(); 5 thread01.interrupt(); 6 Thread.sleep(10); 7 System.out.println(thread01.isInterrupted()); 8 System.out.println(thread01.isInterrupted()); 9 System.out.println(thread01.isInterrupted()); 10 } 11}
结果:
1false 2false 3false
可以看到,虽然调用了interrupt()方法,但是在调用isInterrupted()方法的时候,线程已经执行完了,所以返回的是false。
看一下反例,调用interrupt()方法,在线程没执行完的时候调用isInterrupted()方法
1public class Thread01 extends Thread{ 2 3 @Override 4 public void run() { 5 for(int i = 0; i < 50000; i++) { 6 7 } 8 } 9}
测试一下:
1public class Test { 2 public static void main(String[] args) throws InterruptedException { 3 Thread01 thread01 = new Thread01(); 4 thread01.start(); 5 thread01.interrupt(); 6 System.out.println(thread01.isInterrupted()); 7 System.out.println(thread01.isInterrupted()); 8 System.out.println(thread01.isInterrupted()); 9 } 10}
结果:
1true 2true 3true
3、interrupted()
该方法的作用就是测试**当前线程(currentThread)**是否已经中断,且线程的中断标识位会被清除,换句话说,连续成功调用两次该方法,第二次的返回值一定是false。注意该方法是一个静态方法。
1 1 /** 2 2 * Tests whether the current thread has been interrupted. The 3 3 * <i>interrupted status</i> of the thread is cleared by this method. In 4 4 * other words, if this method were to be called twice in succession, the 5 5 * second call would return false (unless the current thread were 6 6 * interrupted again, after the first call had cleared its interrupted 7 7 * status and before the second call had examined it). 8 8 * 9 9 * <p>A thread interruption ignored because a thread was not alive 1010 * at the time of the interrupt will be reflected by this method 1111 * returning false. 1212 * 1313 * @return <code>true</code> if the current thread has been interrupted; 1414 * <code>false</code> otherwise. 1515 * @see #isInterrupted() 1616 * @revised 6.0 1717 */ 1818 public static boolean interrupted() { 1919 return currentThread().isInterrupted(true); 2020 } 21 221 private native boolean isInterrupted(boolean ClearInterrupted);
①:注意一下第9-11行,若是调用interrupted()方法时,当前已经调用interrupt()方法的线程was not alive,方法会返回false,而不是true。
②:最终调用的是isInterrupted(boolean ClearInterrupted),这个方法是一个native的,看得出也是Java虚拟机实现的。方法的参数ClearInterrupted,顾名思义,清除中断标识位,这里传递true,明显就是清除线程的中断标识位。
此外,JDK API中有些类的方法也可能会调用中断,比如FutureTask的cancel,如果传入true则会在正在运行的异步任务上调用interrupt()方法,又如ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt()方法。这些场景下只要代码没有对中断作出响应,那么任务将一直执行下去。
举例1:说明interrupted()方法测试的是当前线程是否被中断
1public class Thread01 extends Thread{ 2 3 private List<Integer> list = new ArrayList<>(); 4 @Override 5 public void run() { 6 System.out.println("开始执行run方法"); 7 for(int i = 0; i < 50000; i++) { 8 list.add(i); 9 } 10 System.out.println("执行run方法结束"); 11 12 } 13}
测试:
1 1 public class Test { 2 2 public static void main(String[] args) throws InterruptedException { 3 3 Thread01 thread01 = new Thread01(); 4 4 thread01.start(); 5 5 Thread.sleep(5); 6 6 thread01.interrupt(); 7 7 System.out.println(Thread.interrupted()); 8 8 System.out.println(thread01.isInterrupted()); 9 9 System.out.println(thread01.isInterrupted()); 1010 } 1111 }
结果:
1开始执行run方法 2false 3true 4true 5执行run方法结束
说明:由8行和9行结果来看,thread01确实是在执行期间被设置了中断标识位,但是第7行结果并没有返回true,这是因为Thread.interrupted()方法测试的当前线程是否处于中断状态,其当前线程是main线程,而main线程并没有处于中断状态,所以返回false。
举例2:若是run方法之行结束了,再来测试thread01是否处于中断状态,那么结果是什么
1public class Thread01 extends Thread{ 2 @Override 3 public void run() { 4 System.out.println("开始执行run方法"); 5 System.out.println("执行run方法结束"); 6 } 7}
测试:sleep方法确保thread01执行完了在测试其是否处于中断状态
1public class Test { 2 public static void main(String[] args) throws InterruptedException { 3 Thread01 thread01 = new Thread01(); 4 thread01.start(); 5 thread01.interrupt(); 6 Thread.sleep(50); 7 System.out.println(Thread.interrupted()); 8 System.out.println(thread01.isInterrupted()); 9 System.out.println(thread01.isInterrupted()); 10 } 11}
结果:
1开始执行run方法 2执行run方法结束 3false 4false 5false
说明:可以看到,即使thread01被设置了中断标识位,但若是线程执行完成了再来测试其是否处于中断状态,那么一定会返回false。
举例3:中断main线程,给main线程设置中断标识位
1public class Thread01 extends Thread{ 2 3 private List<Integer> list = new ArrayList<>(); 4 @Override 5 public void run() { 6 System.out.println("开始执行run方法"); 7 for(int i = 0; i < 50000; i++) { 8 list.add(i); 9 } 10 System.out.println("执行run方法结束"); 11 } 12}
测试:
1public class Test { 2 public static void main(String[] args) throws InterruptedException { 3 Thread01 thread01 = new Thread01(); 4 thread01.start(); 5 thread01.interrupt(); 6 Thread.sleep(5); 7 Thread.currentThread().interrupt(); 8 System.out.println(Thread.interrupted()); 9 System.out.println(Thread.interrupted()); 10 System.out.println(thread01.isInterrupted()); 11 System.out.println(thread01.isInterrupted()); 12 } 13}
结果:
1开始执行run方法 2true 3false 4true 5true 6执行run方法结束
三、中断处理时机
这其实是一个很宽泛的、没有标注答案的话题。显然,作为一种协作机制,不会强求被中断的线程一定要在某个点进行中断处理。实际上,被中断线程只需要在合适的时候处理即可,如果没有合适的时间点,甚至可以不处理。"合适的时间点"就和业务逻辑密切相关了。
处理时机决定着程序的效率和响应的灵敏度。频繁的检查中断可能会导致程序执行效率低下,较少的检查则可能导致中断请求得不到及时响应。在实际场景中,如果性能指标比较关键,可能需要建立一个测试模型来分析最佳的中断检测点,以平衡性能和响应灵敏性。
四、线程中断举例
示例1:线程被设置了中断标识位且没有抛出异常:InterruptedException
1public class Thread01 extends Thread{ 2 3 @Override 4 public void run() { 5 6 while(true){ 7 if(Thread.currentThread().isInterrupted()){ 8 System.out.println(Thread.currentThread().getName() + "被中断了"); 9 break; 10 }else{ 11 System.out.println(Thread.currentThread().getName() + "在运行中"); 12 } 13 } 14 15 } 16}
测试:
1public class Test { 2 public static void main(String[] args) throws InterruptedException { 3 Thread01 thread01 = new Thread01(); 4 thread01.start(); 5 Thread.sleep(1000); 6 thread01.interrupt(); 7 } 8}
结果:
1....... 2Thread-0在运行中 3Thread-0在运行中 4Thread-0在运行中 5Thread-0在运行中 6Thread-0在运行中 7Thread-0在运行中 8Thread-0在运行中 9Thread-0被中断了
代码分为以下几步:
1、main函数起一个thread01线程
2、main函数1秒钟之后给t线程打一个中断标识位,表示thread01线程要中断
3、thread01线程无限轮询自己的中断标识位,中断了则打印、退出,否则一直运行
从控制台上打印的语句看到,1秒钟中断后,就停止了。那这种场景就是前面说的"频繁地检查",导致程序效率低下;那如果不频繁地检查呢,比如在while中的else分支中加上Thread.sleep(500),表示500ms即0.5s检查一次,那这种场景就是前面说的"中断得不到及时的响应"。
其实这个例子中,thread01线程完全可以不用去管这个中断标识位的,不去检查就好了,只管做自己的事情,这说明中断标识位设不设置是别人的事情,处不处理是我自己的事情,没有强制要求必须处理中断。按照这个例子理解就是,main线程中给thread01线程设置了中断标识位,但是thread01线程处不处理就是它自己的事情了。
但是,那些会抛出InterruptedException的方法要除外。像sleep、wait、notify、join,这些方法遇到中断必须有对应的措施,可以直接在catch块中处理,也可以抛给上一层。这些方法之所以会抛出InterruptedException就是由于Java虚拟机在实现这些方法的时候,本身就有某种机制在判断中断标识位,如果中断了,就抛出一个InterruptedException。
示例2:若线程被设置了中断标识位,调用sleep方法时会抛出异常
1public class Thread01 extends Thread{ 2 3 @Override 4 public void run() { 5 6 while(true){ 7 if(Thread.currentThread().isInterrupted()){ 8 System.out.println(Thread.currentThread().getName() + "被中断了"); 9 break; 10 }else{ 11 try { 12 Thread.sleep(400); 13 System.out.println(Thread.currentThread().getName() + "运行中"); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 System.out.println("sleep方法抛出了异常"); 17 break; 18 } 19 } 20 } 21 22 } 23}
测试:
1public class Test { 2 public static void main(String[] args) throws InterruptedException { 3 Thread01 thread01 = new Thread01(); 4 thread01.start(); 5 Thread.sleep(1000); 6 thread01.interrupt(); 7 } 8}
结果:

参考资料: