系列文章目录java
线程中断是一个很重要的概念,一般,取消一个任务的执行,最好的,同时也是最合理的方法,就是经过中断。segmentfault
本篇咱们主要仍是经过源码分析来看看中断的概念。安全
本文的源码基于JDK1.8less
java线程的中断机制为咱们提供了一个契机,使被中断的线程可以有机会从当前的任务中跳脱出来。而中断机制的最核心的两个概念就是interrupt status
和 InterruptedException
。ide
java中对于中断的大部分操做无外乎如下两点:函数
InterruptedException
在java中,每个线程都有一个中断标志位,表征了当前线程是否处于被中断状态,咱们能够把这个标识位理解成一个boolean类型的变量,当咱们中断一个线程时,将该标识位设为true,当咱们清除中断状态时,将其设置为false, 其伪代码以下:源码分析
(注意,本文的伪代码部分是我我的所写,并不权威,只是帮助我本身理解写的)this
// 注意,这是伪代码!!! // 注意,这是伪代码!!! // 注意,这是伪代码!!! public class Thread implements Runnable { private boolean interruptFlag; // 中断标志位 public boolean getInterruptFlag() { return this.interruptFlag; } public void setInterruptFlag(boolean flag) { this.interruptFlag = flag; } }
然而,在Thread线程类里面,并无相似中断标志位的属性,可是提供了获取中断标志位的接口:线程
/** * Tests if some Thread has been interrupted. The interrupted state * is reset or not based on the value of ClearInterrupted that is * passed. */ private native boolean isInterrupted(boolean ClearInterrupted);
这是一个native方法,同时也是一个private方法,该方法除了可以返回当前线程的中断状态,还能根据ClearInterrupted
参数来决定要不要重置中断标志位(reset操做至关于上面的interruptFlag = false
)。code
Thread类提供了两个public方法来使用该native方法:
public boolean isInterrupted() { return isInterrupted(false); } public static boolean interrupted() { return currentThread().isInterrupted(true); }
其中isInterrupted
调用了isInterrupted(false)
, ClearInterrupted
参数为false
, 说明它仅仅返回线程实例的中断状态,可是不会对现有的中断状态作任何改变,伪代码能够是:
// 注意,这是伪代码!!! // 注意,这是伪代码!!! // 注意,这是伪代码!!! public boolean isInterrupted() { return interruptFlag; //直接返回Thread实例的中断状态 }
而interrupted
是一个静态方法,因此它能够由Thread类直接调用,天然就是做用于当前正在执行的线程,因此函数内部使用了currentThread()方法,与isInterrupted()
方法不一样的是,它的ClearInterrupted
参数为true
,在返回线程中断状态的同时,重置了中断标识位,伪代码能够是:
// 注意,这是伪代码!!! // 注意,这是伪代码!!! // 注意,这是伪代码!!! public static boolean interrupted() { Thread current = Thread.currentThread(); // 获取当前正在执行的线程 boolean interruptFlag = current.getInterruptFlag(); // 获取线程的中断状态 current.setInterruptFlag(false); // 清除线程的中断状态 return interruptFlag; //返回线程的中断状态 }
可见,isInterrupted()
和 interrupted()
方法只涉及到中断状态的查询,最可能是多加一步重置中断状态,并不牵涉到InterruptedException
。
不过值得一提的是,在咱们能使用到的public方法中,interrupted()
是咱们清除中断的惟一方法。
咱们直接来看的源码:
/** * Thrown when a thread is waiting, sleeping, or otherwise occupied, * and the thread is interrupted, either before or during the activity. * Occasionally a method may wish to test whether the current * thread has been interrupted, and if so, to immediately throw * this exception. The following code can be used to achieve * this effect: * <pre> * if (Thread.interrupted()) // Clears interrupted status! * throw new InterruptedException(); * </pre> * * @author Frank Yellin * @see java.lang.Object#wait() * @see java.lang.Object#wait(long) * @see java.lang.Object#wait(long, int) * @see java.lang.Thread#sleep(long) * @see java.lang.Thread#interrupt() * @see java.lang.Thread#interrupted() * @since JDK1.0 */ public class InterruptedException extends Exception { private static final long serialVersionUID = 6700697376100628473L; /** * Constructs an <code>InterruptedException</code> with no detail message. */ public InterruptedException() { super(); } /** * Constructs an <code>InterruptedException</code> with the * specified detail message. * * @param s the detail message. */ public InterruptedException(String s) { super(s); } }
上面的注释是说,在线程处于“waiting, sleeping”甚至是正在运行的过程当中,若是被中断了,就能够抛出该异常,咱们先来回顾一下咱们前面遇到过的抛出InterruptedException
异常的例子:
(1) wait(long timeout)方法中的InterruptedException
/* * * @param timeout the maximum time to wait in milliseconds. * @throws IllegalArgumentException if the value of timeout is * negative. * @throws IllegalMonitorStateException if the current thread is not * the owner of the object's monitor. * @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The <i>interrupted * status</i> of the current thread is cleared when * this exception is thrown. * @see java.lang.Object#notify() * @see java.lang.Object#notifyAll() */ public final native void wait(long timeout) throws InterruptedException;
该方法的注释中提到,若是在有别的线程在当前线程进入waiting状态以前或者已经进入waiting状态以后中断了当前线程,该方法就会抛出InterruptedException
,同时,异常抛出后,当前线程的中断状态也会被清除。
(2) sleep(long millis)方法中的InterruptedException
/* @param millis * the length of time to sleep in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * if any thread has interrupted the current thread. The * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public static native void sleep(long millis) throws InterruptedException;
与上面的wait方法一致,若是当前线程被中断了,sleep方法会抛出InterruptedException,而且清除中断状态。
若是有其余方法直接或间接的调用了这两个方法,那他们天然也会在线程被中断的时候抛出InterruptedException,而且清除中断状态。例如:
这里值得注意的是,虽然这些方法会抛出InterruptedException,可是并不会终止当前线程的执行,当前线程能够选择忽略这个异常。
也就是说,不管是设置interrupt status
仍是抛出InterruptedException
,它们都是给当前线程的建议,当前线程能够选择采纳或者不采纳,它们并不会影响当前线程的执行。
至于在收到这些中断的建议后,当前线程要怎么处理,也彻底取决于当前线程。
上面咱们说了怎么检查(以及清除)一个线程的中断状态,提到当一个线程被中断后,有一些方法会抛出InterruptedException。
下面咱们就来看看怎么中断一个线程。
要中断一个线程,只需调用该线程的interrupt
方法,其源码以下:
/** * Interrupts this thread. * * <p> Unless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. * * <p> If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. * * <p> If this thread is blocked in an I/O operation upon an {@link * java.nio.channels.InterruptibleChannel InterruptibleChannel} * then the channel will be closed, the thread's interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. * * <p> If this thread is blocked in a {@link java.nio.channels.Selector} * then the thread's interrupt status will be set and it will return * immediately from the selection operation, possibly with a non-zero * value, just as if the selector's {@link * java.nio.channels.Selector#wakeup wakeup} method were invoked. * * <p> If none of the previous conditions hold then this thread's interrupt * status will be set. </p> * * <p> Interrupting a thread that is not alive need not have any effect. * * @throws SecurityException * if the current thread cannot modify this thread * * @revised 6.0 * @spec JSR-51 */ public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }
上面的注释很长,咱们一段一段来看:
/** * Interrupts this thread. * * <p> Unless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. ... */
上面这段首先说明了这个函数的目的是中断这个线程,这个this thread
,固然指的就是该方法所属的线程对象所表明的线程。
接着说明了,一个线程老是被容许中断本身,可是咱们若是想要在一个线程中中断另外一个线程的执行,就须要先经过checkAccess()检查权限。这有可能抛出SecurityException异常, 这段话用代码体现为:
if (this != Thread.currentThread()) checkAccess();
咱们接着往下看:
/* ... * <p> If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. ... */
上面这段是说,若是线程由于如下方法的调用而处于阻塞中,那么(调用了interrupt方法以后),线程的中断标志会被清除,而且收到一个InterruptedException:
Object的方法
Thread的方法
关于这一点,咱们上面在分析InterruptedException的时候已经分析过了。
这里插一句,因为上面这些方法在抛出InterruptedException异常后,会同时清除中断标识位,所以当咱们此时不想或者没法传递InterruptedException异常,也不对该异常作任何处理时,咱们最好经过再次调用interrupt来恢复中断的状态,以供上层调用者处理,这一点,咱们在逐行分析AQS源码(二): 锁的释放的最后就说明过这种用法。
接下来的两段注释是关于NIO的,咱们暂时不看,直接看最后两段:
/* ... * <p> If none of the previous conditions hold then this thread's interrupt * status will be set. </p> * * <p> Interrupting a thread that is not alive need not have any effect. */
这段话是说:
注释看完了以后咱们再来看代码部分,其实代码部分很简单,中间那段同步代码块是和NIO有关的,咱们能够暂时无论,整个方法的核心调用就是interrupt0()
方法,而它是一个native方法:
private native void interrupt0();
这个方法所作的事情很简单:
Just to set the interrupt flag
因此,至此咱们明白了,所谓“中断一个线程”,其实并非让一个线程中止运行,仅仅是将线程的中断标志设为true
, 或者在某些特定状况下抛出一个InterruptedException,它并不会直接将一个线程停掉,在被中断的线程的角度看来,仅仅是本身的中断标志位被设为true
了,或者本身所执行的代码中抛出了一个InterruptedException
异常,仅此而已。
既然上面咱们提到了,中断一个线程并不会使得该线程中止执行,那么咱们该怎样终止一个线程的执行呢。早期的java中提供了stop()方法来中止一个线程,可是这个方法是不安全的,因此已经被废弃了。如今终止一个线程,基本上只能靠“曲线救国”式的中断来实现。
前面咱们说过,当一个线程由于调用wait,sleep,join方法而进入阻塞状态后,若在这时中断这个线程,则这些方法将会抛出InterruptedException
异常,咱们能够利用这个异常,使线程跳出阻塞状态,从而终止线程。
@Override public void run() { while(true) { try { // do some task // blocked by calling wait/sleep/join } catch (InterruptedException ie) { // 若是该线程被中断,则会抛出InterruptedException异常 // 咱们经过捕获这个异常,使得线程从block状态退出 break; // 这里使用break, 可使咱们在线程中断后退出死循环,从而终止线程。 } } }
与中断一个处于阻塞状态所不一样的是,中断一个处于运行状态的线程只会将该线程的中断标志位设为true
, 而并不会抛出InterruptedException异常,为了能在运行过程当中感知到线程已经被中断了,咱们只能经过不断地检查中断标志位来实现:
@Override public void run() { while (!isInterrupted()) { // do some task... } }
这里,咱们每次循环都会先检查中断标志位,只要当前线程被中断了,isInterrupted()
方法就会返回true
,从而终止循环。
上面咱们分别介绍了怎样终止一个处于阻塞状态或运行状态的线程,若是咱们将这两种方法结合起来,那么就能够同时应对这两种情况,从而可以终止任意一个存活的线程:
@Override public void run() { try { // 1. isInterrupted() 用于终止一个正在运行的线程。 while (!isInterrupted()) { // 执行任务... } } catch (InterruptedException ie) { // 2. InterruptedException异经常使用于终止一个处于阻塞状态的线程 } }
不过使用这二者的组合必定要注意,wait,sleep,join等方法抛出InterruptedException有一个反作用: 清除当前的中断标志位,因此不要在异常抛出后不作任何处理,而寄望于用isInterrupted()
方法来判断,由于中标志位已经被重置了,因此下面这种写法是不对的:
@Override public void run() { //isInterrupted() 用于终止一个正在运行的线程。 while (!isInterrupted()) { try { // 执行任务... } } catch (InterruptedException ie) { // 在这里不作任何处理,仅仅依靠isInterrupted检测异常 } } }
这个方法中,在catch块中咱们检测到异常后没有使用break方法跳出循环,而此时中断状态已经被重置,当咱们再去调用isInterrupted
,依旧会返回false
, 故线程仍然会在while循环中执行,没法被中断。
Java没有提供一种安全直接的方法来中止某个线程,可是提供了中断机制。对于被中断的线程,中断只是一个建议,至于收到这个建议后线程要采起什么措施,彻底由线程本身决定。
中断机制的核心在于中断状态和InterruptedException
异常
中断状态:
Thread.interrupted方法同时会返回线程原来的中断的状态。
若是仅仅想查看线程当前的中断状态而不清除原来的状态,则应该使用Thread#isInterrupted。
某些阻塞方法在抛出InterruptedException异常后,会同时清除中断状态。若不能对该异常作出处理也没法向上层抛出,则应该经过再次调用interrupt方法恢复中断状态,以供上层处理,一般状况下咱们都不该该屏蔽中断请求。
中断异常:
中断异常通常是线程被中断后,在一些block类型的方法(如wait
,sleep
,join
)中抛出。
咱们可使用Thread#interrupt中断一个线程,被中断的线程所受的影响为如下两种之一:
不管是中断状态的改变仍是InterruptedException
被抛出,这些都是当前线程能够感知到的"建议",若是当前线程选择忽略这些建议(例如简单地catch住异常继续执行),那么中断机制对于当前线程就没有任何影响,就好像什么也没有发生同样。
因此,中断一个线程,只是传递了请求中断的消息,并不会直接阻止一个线程的运行。
(完)
查看更多系列文章:系列文章目录