您的位置:首页 > 编程语言 > Java开发

java多线程之中断(interrupt)问题

2017-07-21 09:23 435 查看

摘要

在java中,想要让一个线程停下来,有三种办法:

(1)采用退出标志,使得run方法执行完之后线程自然终止。

(2)使用stop强行终止线程,但该方法由于安全问题已经被deprecated。

(3)使用中断机制。

引入

第一种方法没特别之处,无非是在覆盖Runnable接口之时对run方法中添加状态标识逻辑。比如:

public class MyThread extends Thread
{
private boolean running;
@Override
public run(){
if(running){
//....business methods
}
}
}


下面详细讨论中断机制。

这里的中断是软中断,即在某个线程中可以将目标线程的中断标志位置位,然后在目标线程进行中断检查的时候来判断自己应当如何响应中断。

特别的,可以以此来停止目标线程。中断的局限性,无法对出于死锁状态的线程起作用。

简单原理:

java.lang.Thread
类中提供了一个方法来将线程的中断标志位置位:

Thread.interrupt()
方法,其函数申明为:
public void interrupt();
该方法会将线程的中断标志位置位。

java.lang.Thread
类还两个方法来检查中断标志:

public static boolean interrupted()
{
return currentThread().isInterrupted(true);
}
public boolean isInterrupted()
{
return isInterrupted(false);
}


这两个方法分别调用同一个原生方法来检查中断状态并有所不同。

一方面
interrupted()
是静态方法,可以使用
Thread.interrupted()
的方法来调用。

isInterrupted()
则需要一个实例对象作为隐式参数,使用
threadInstance.isInterrupted()
来调用。

另一方面,interrupted在调用后会立即将线程中断标志位复位,而isInterrupted则不会这么做。

非阻塞线程的中断问题:

调用Thread.interrupt将目标线程中断标志位置位,然后在目标线程中检查中断标志位。

如果检查到中断之后使用throw new InterruptedException()抛出一个异常,然后使用catch语句来捕获这个异常,则可以在这里完成一些中断逻辑。

特别的,可以使用这种方法退出线程。下面是这种退出方式的举例。

package com.kingbaron.jinterrupt;
/**
* Created by kingbaron on 2016/3/17.
*/
public class UnblockingInterrupt extends Thread{
@Override
public void run(){
super.run();
try{
for(int i=0;i<10000;i++)
{
if(Thread.interrupted())
//if(this.isInterrupted())
{
System.out.println("I'm interrupted and I'm going out");
throw new InterruptedException();
}
System.out.println("i="+(i+1));
}
System.out.println("I'm below the for clauses and I have no chance to run here");
}catch(InterruptedException e){
System.out.println("I'm in the catch clauses of the method UnblockingInterrupt.run()");
e.printStackTrace();
}
}
}


package com.kingbaron.jinterrupt;
/**
* Created by kingbaron on 2016/3/17.
*/
public class TestUnblockingInterrupt {
public static void main(String[] args){
try{
UnblockingInterrupt thread=new UnblockingInterrupt();
thread.start();
Thread.sleep(2000);
thread.interrupt();
}catch(InterruptedException e)
{
System.out.println("main catch");
e.printStackTrace();
}
}
}


输出结果(关键部分)为:

i=277919
i=277920
I'm interrupted and I'm going out
I'm in the catch clauses of the method UnblockingInterrupt.run
java.lang.InterruptedException
at com.kingbaron.jinterrupt.UnblockingInterrupt.run(UnblockingInterrupt.java:16)


可以看到,当线程thread的中断标志位被main线程中使用thread.interrupt置位后,在执行thread线程的for循环的if条件检查时,

Thread.interrupted()返回true随后在其中抛出一个InterruptedException异常,再使用catch子句捕获该异常,由于run方法中在catch子句之后再无语句,

故线程从run方法返回,thread线程就此终止。

阻塞线程的中断问题。

首先,一个线程出于阻塞中断状态(实际上还包括等待状态)的原因,常见的有线程调用了thread.sleep、

thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法等。

如果一个线程处于阻塞状态,若将其中断标志位置为,则会在产生阻塞的语句出抛出一个InterruptedException异常,

并且在抛出异常后立即将线程的中断标示位复位,即重新设置为false。

抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。

换言之,中断将一个处于阻塞状态的线程的阻塞状态解除。

注意,synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。

与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的。

即如果发
4000
生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。

但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。

你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。

没有任何语言方面的需求一个被中断的线程应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。

某些线程非常重要,以至于它们应该不理会中断,而是在处理完抛出的异常之后继续执行,但是更普遍的情况是,一个线程将把中断看作一个终止请求。

这种线程的run方法遵循如下形式:

public void run() {
try {
...
/*
* 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上
* !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显
* 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时。
*/
while (!Thread.currentThread().isInterrupted()&& more work to do) {
do more work
}
} catch (InterruptedException e) {
//线程在wait或sleep期间被中断了
} finally {
//线程结束前做一些清理工作
}
}


在这里,应当注意一个原则:尽量不要在底层代码中捕获InterruptedException原则。因为该异常一旦被捕获,线程的中断标志位就会被置位。

对于底层代码而言,如果代码会抛出不知道应当如何应对的InterruptException异常,可以有下面两个选择。

(1)捕获到InterruptedException异常后将中断标志位置位,让外层代码根据检查中断标志位来判断是否中断。

public subTask(){
try{
//...the works may throw a InterruptedException
}catch(InterruptedException e){
Thread.currentThread.interrupt();
}
}


(2)更推荐的方法是,底层代码不捕获InterruptedException,直接将其抛给外层代码去解决

public subTask() throws InterruptedException
{
//...
}


中断失效的情况之临界区:

进入临界区的代码是不允许中断的。这一点很好理解,临界区是并行问题为了保护临界资源的互斥访问而特地加锁的,

一旦可以中断,那么锁的存在也就没有任何意义了。特别的,形成了死锁的线程会分别处于阻塞状态,但是它们都无法被中断。

常见的有进入synchronized块的代码以及Lock.lock()之后没能得到锁而处于阻塞的状态。通常可以使用Lock.lockInterruptibly()来代替Lock.lock(),

因为Lock.lockInterruptibly()可以接受中断,这个中断指的是,既然没能得到锁进入临界区,与其阻塞不如做些别的什么事。

一旦进入临界区,任何方法都不得中断。

中断失效的情况之不可中断I/O:

自java 1.4之后对大量数据的I/O常用通道(channels)机制,它是可以被中断的I/O,也就是说如果这种I/O处于阻塞状态,可以使用中断来解除阻塞。

但是存在着一些不可中断的操作,比如ServerSocket.accept(),inputSteam.read()等调用interrupt()对于这几个问题无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去。

对于inputStream等资源,有些(实现了interruptibleChannel接口)可以通过close()方法将资源关闭,对应的阻塞也会被放开。

对于处理大型I/O时,推荐使用Channels。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程