多线程: 多线程中断机制
在 java中启动线程非常容易,大多数情况下是让一个线程执行完自己的任务然后自己停掉。一个线程在未正常结束之前, 被强制终止是很危险的事情. 因为它可能带来完全预料不到的严重后果,比如会带着自己所持有的锁而永远的休眠,迟迟不归还锁等。在当前的api中,Thread.suspend、Thread.stop等方法都被Deprecated了,线程只能用interrupt中断,而且不是立刻中断,只是发了一个类似于信号量的东西,通过修改了被调用线程的中断状态来告知那个线程, 说它被中断了,至于什么时候中断,这个有系统判断,会在一个合适的时候进行中断处理。
/**
* Created by Zero on 2017/8/17.
*/
public class ThreadTest1 {
public static void main(String[] args) {
NThread nThread = new NThread();
System.out.println("interrupt执行前");
nThread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
nThread.interrupt();
System.out.println("interrupt执行后");
}
/**
* 测试多线程的中断机制
*/
static class NThread extends Thread{
@Override
public void run() {
super.run();
while(true){
System.out.println("依然存活...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
在上面代码中,线程睡醒之后,调用thread线程的interrupt方法,catch到InteruptedException,设置标志位。interrupt()方法相当于线程睡着的时候一盆凉水来吵醒它,线程表示不开心,并向你抛出一个大大的异常以示不满。
- 首先:线程中断是一种协作机制,调用线程对象的interrupt方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时间中断自己。
- 其次:任务的方法必须是可中断的,即方法必须抛出InterruptedException。
由此可见,interrupt() 方法并不能立即中断线程,该方法仅仅告诉线程外部已经有中断请求,至于是否中断还取决于线程自己。在Thread类中除了interrupt() 方法还有另外两个非常相似的方法:interrupted 和 isInterrupted 方法,下面来对这几个方法进行说明:
- interrupt 此方法是实例方法,用于告诉此线程外部有中断请求,并且将线程中的中断标记设置为true,而不是立即中断。
- interrupted 此方法是类方法,用来判断当前线程是否已经中断。线程的中断状态由该方法清除。
- isInterrupted 此方法是实例方法,用来判断线程是否已经中断。线程的中断状态不受该方法的影响。
总结:java线程中断机制通过调用Thread.interrupt() 方法来做的,这个方法通过修改了被调用线程的中断状态来告知那个线程说它被中断了。对于非阻塞中的线程,只是改变了中断状态,即Thread.isInterrupted() 将返回true;对于可取消的阻塞状态中的线程,比如等待在这些函数上的线程,Thread.sleep()、Object.wait()、Thread.join(), 这个线程收到中断信号后,会抛出InterruptedException,同时会把中断状态置回为true。但调用Thread.interrupted()会对中断状态进行复位。
/**
* Created by Zero on 2017/8/17.
*/
public class ThreadTest1 {
public static void main(String[] args) {
NThread nThread = new NThread();
nThread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("cancel执行前" + System.currentTimeMillis());
nThread.cancel();
}
/**
* 测试多线程的中断机制
*/
static class NThread extends Thread {
private boolean isCancel;
@Override
public void run() {
while (!isCancel) {
System.out.println("依然存活...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
System.out.println("while结束" + System.currentTimeMillis());
}
public void cancel() {
this.isCancel = true;
}
}
}
执行结果:
依然存活...
cancel执行前1502964413042
while结束1502964415042
机智的你,此刻一定发现执行前后相差2000毫秒,难道cancel()方法执行了2000毫秒?这纯属扯淡,里面没有任何耗时操作,就是一个赋值而已,其实是子线程的退出,前提条件是while循环结束,当且仅当cancel标示设置为true的瞬间立马执行while的判断,此时的时间差才可以忽略不计(1毫秒内),但是当我们调用cancel方法将isCancel 标记设置为true 时,while循环里面有一个耗时操作(休眠5000毫秒),只有等待耗时操作执行完毕后才会去检查这个标记,所以cancel方法和线程退出中间有时间间隔。
接下来,我们通过interrupt 和 isinterrupt 方法来中断线程,代码如下:
/**
* Created by Zero on 2017/8/17.
*/
public class ThreadTest1 {
public static void main(String[] args) {
NThread nThread = new NThread();
nThread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("interrupt执行前"+System.currentTimeMillis());
nThread.interrupt();
}
/**
* 测试多线程的中断机制
*/
static class NThread extends Thread{
@Override
public void run() {
while(!interrupted()){
System.out.println("依然存活...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("InterruptedException");
Thread.currentThread().interrupt();
}
}
System.out.println("interrupt执行后"+System.currentTimeMillis());
}
}
}
依然存活...
interrupt执行前1502965915683
InterruptedException
interrupt执行后1502965915683
这次立马中断了,是因为在开启子线程后,经过执行3000毫秒的休眠,线程执行了interrupt 方法,此时子线程的5000毫秒休眠还没结束,就像上述所说的睡眠中被一盆冷水吵醒,很不开心的抛了异常,Thread.currentThread().interrupt() 改变了线程的标记状态,在抛出InterruptedException 的同时,线程的中断标志被清除了,再次执行while循环语句的时候,!interrupted() 此时是false,便不再执行while语句。
此处如果去掉Thread.currentThread().interrupt() ,线程便会无休止的执行下去,此处就不上代码了,就注释掉这一行,运行就可以看到效果,经常看到一些代码在catch中不作任何处理,其实有时候这样是很危险的,此处已经证明。
两点建议:
1. 除非你知道线程的中断策略,否则不应该中断它。
2. 任务代码不该猜测中断对执行线程的含义。遇到InterruptedException异常时,不应该将其捕获后“吞掉”,而应该继续向上层代码抛出。