Java中如果需要终止线程可以有以下一些方式:
- 线程正常运行退出,这个是比较常见的run()方法运行后退出。
- 调用stop()方法,非常粗暴,已经标记为@Deprecated,所以不再使用。
- 使用中断机制。
这里只看第三种,interrupt()方法,很多文章讲interrupt()没讲清楚,经常是不明就里,一头雾水。留给我们一些疑惑,中断是啥,中断异常即InterruptedException又是什么时候出现的。开头先给出实践的的结论。 InterruptedException是什么时候产生的:InterruptedException是由于调用了线程的interrupt()方法产生的,但是调用interrupt()方法不一定会产生InterruptedException。怎么理解,直接上代码
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
}catch (Exception e) {
e.printStackTrace();
}
}
});
threadA.start();
threadA.interrupt();
}
打印信息如下:当前线程退出了。
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at juc.InteruptTest2$1.run(InteruptTest2.java:19)
at java.lang.Thread.run(Thread.java:748)
这里新建了一个线程A,他的运行逻辑是睡眠两秒后正常退出,但是我们在main线程中调用了A的interrupt()方法,导致他中途就抛出异常退出了,这里就有一个关键问题了,interrupt()方法和sleep()方法究竟是咋整的。首先我们来看一下sleep()方法的源码描述:
Causes the currently executing thread to sleep (temporarily cease
execution) for the specified number of milliseconds, subject to
the precision and accuracy of system timers and schedulers. The thread
does not lose ownership of any monitors.
@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.
可以看到是native方法,也就是通过JNI(Java Native Interface)机制调用的系统的C语言方法。我们重点看一下注释,这里的意思是线程调用该方法后会变成阻塞状态,在指定时间内让出系统资源不在参与线程调度,并且如果线程如果持有某个对象的监视器锁是不会释放的,也即是其他线程如果试图获取锁会失败。这里就好理解了。
我们再来看会抛出哪些异常,一个是参数校验异常IllegalArgumentException; 一个是中断异常InterruptedException,抛出原因描述:如果有其他线程对当前线程进行中断,则会抛出该异常,并且该线程的中断状态会被清除。所以重点来了,中断状态,也就是说调用了interrupt()方法后会设置中断状态为true,然后sleep()方法在阻塞过程中会监控到中断状态为true,则抛出InterruptedException。
那么为什么阻塞过程还能监控中断状态呢,答案很简单,要么是Jvm要么是操作系统会监控该状态然后抛出异常。讲到这里我们再去看一些interrupt()方法的某一段注释
....
If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int)
methods of the Object class, or of the join(), join(long), join(long, int), sleep(long),
or sleep(long, int), methods of this class, then its interrupt status will be cleared and
it will receive an InterruptedException.
....
翻译过来就是: 如果该线程由于调用继承于Object类的wait()方法以及相应的重载方法,或者该线程由于调用了继承于Thread类的的sleep(),join()等方法,在被阻塞的过程中由于调用了interrupt()方法,会抛出InterruptedException异常。
说到这里,可以对中断状态原理总结一下: 调用interrupt()方法方法会给线程设置一个中断状态,但是不会对线程造成中断,中断是需要程序自己去操作的,如果线程还在阻塞中而被调用的话,会由jvm抛出中断异常,而如果是线程正常运行被调用了中断方法,如果我们的程序中没有进行判断,实际上没有任何变化。 我们不妨看两个例子。
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("Thread is running");
}
}
});
threadA.start();
threadA.interrupt();
}
上面例子中,线程的运行逻辑就是一直打印信息,然后启动线程后立即调用中断方法,但是线程不会中断,因此,如果我们程序有中断的逻辑需求的话,需要进行判断。判断程序是否中断可以使用isInterrupted()方法。
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()) {
System.out.println("Thread is running");
}
}
});
threadA.start();
//这里主线程睡眠1秒,不至于太快中断线程A导致没有打印
Thread.sleep(1000);
threadA.interrupt();
}
介绍完了上述两个方法,我们再来看另一个检查线程是否中断的方法,interrupted()。interrupted()是static方法,该方法的作用是返回当前线程的中断标记,并且如果是true的话,会返回并且重新把中断状态清除,也就是把中断状态设置为false. 这里有两个注意点,1是当前线程,2是中断清除。 我们首先来看什么是当前线程,这个理解了很好理解,不理解的话很容易混淆。我们来看一个例子
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
//打印的是线程的信息
System.out.println(Thread.currentThread().getName());
}
});
threadA.start();
Thread.sleep(1000);
//打印的是main线程的信息
System.out.println(Thread.currentThread().getName());
}
输出信息如下
Thread-0
main
那么我们再来看另外一个例子,关于中断的
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
}
}
});
threadA.start();
System.out.println("threadA的中断状态:" + threadA.isInterrupted());
//中断线程A
threadA.interrupt();
System.out.println("threadA的中断状态:" + threadA.isInterrupted());
//中断当前线程并重置
threadA.interrupted();
System.out.println("threadA的中断状态:" + threadA.isInterrupted());
}
输出信息如下。
threadA的中断状态:false
threadA的中断状态:true
threadA的中断状态:true
第一次调用isInterrupted()方法的时候返回false没有理解误区, 然后进行了threadA的interrupt()调用 第二次调用isInterrupted()方法的时候返回为true没有理解误区 当调用threadA.interrupted()的时候,根据他的定义,返回并重置中断状态为false 第三次调用isInterrupted()的时候,可能有小伙伴会觉得是false,实际并不是 因为interrupted()方法是作用于当前线程的,而这里当前线程是main线程而不是threadA 所以我们必须在程序内部逻辑调用才行,再看另一个例子。
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadA的中断状态:" + Thread.currentThread().isInterrupted());
//只有检查到当前线程被中断会退出循环
while (Thread.currentThread().interrupted() == false) {
}
//线程中断状态重置了,还是返回false
System.out.println("threadA的中断状态:" + Thread.currentThread().isInterrupted());
}
});
threadA.start();
//让主线程先睡眠1秒,不然逻辑不正确
Thread.sleep(1000);
threadA.interrupt();
}
输出如下
threadA的中断状态:false
threadA的中断状态:false
把while的条件改一下,再看看输出
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadA的中断状态:" + Thread.currentThread().isInterrupted());
//只有检查到当前线程被中断会退出循环
while (Thread.currentThread().isInterrupted() == false) {
}
//线程中断状态重置了,还是返回false
System.out.println("threadA的中断状态:" + Thread.currentThread().isInterrupted());
}
});
threadA.start();
//让主线程先睡眠1秒,不然逻辑不正确
Thread.sleep(1000);
threadA.interrupt();
}
输出
threadA的中断状态:false
threadA的中断状态:true
看到了吧,就是中断状态被重置和没有重置的区别。
好了,线程中断的基本知识讲完了,我们总结一下
总结:
- 调用interrupt()方法方法会给线程设置一个中断状态,但是不会对线程造成中断,中断是需要程序自己去操作的。
- 调用isInterrupted()可以获取线程的中断状态。
- 调用interrupted()方法可以获取当前线程的中断状态,如果为true会重置为false。
|