理解java中断

Java中断机制是一种协作机制,即通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。

中断相关的方法:

public class Thread{  
public void interrupt(){}    //中断目标线程,实际上就是设置中断状态为true  
public boolean isInterrupted(){} //返回目标线程的中断状态  
public static boolean interrupted(){} //清除当前线程的中断状态,并返回它之前的值  
} 

非静态方法和静态方法的区别:

//调用native方法,判断线程是否被中断,不清除中断状态
 public boolean isInterrupted() {
     return isInterrupted(false);
 }
  //调用native方法,判断线程是否被中断,清除中断状态
 public static boolean interrupted() {
     return currentThread().isInterrupted(true);
 }

  //native方法,测试线程是否被中断。ClearInterrupted决定是否需要清除中断状态
 private native boolean isInterrupted(boolean ClearInterrupted);

JVM内部确实为每个线程维护了一个中断标记。但应用程序不能直接访问这个中断变量,必须通过上面几个方法进行操作。通常情况下,调用线程的interrupt方法,并不能立即引发中断,只是设置了JVM内部的中断标记。因此,通过检查中断标记,应用程序可以做一些特殊操作,也可以完全忽略中断。

哪些场景可以用到中断?

  • 如果一个方法声明抛出InterruptedException,表示该方法是可中断的。比如sleep、wait、join等。
//Thread类,native方法,当前线程休眠mills毫秒。如果其他线程中断当前线程,则当前线程抛出异常InterruptedException的时候清除当前线程的中断状态。   
  public static native void sleep(long millis) throws InterruptedException;

//Object类  
public final native void wait(long timeout) throws InterruptedException;
public final native void notify();
public final native void notifyAll();
  • 自定义的可中断方法。需要在适合处理中断的地方检测线程中断状态并处理。
if(Thread.currentThread().isInterrupted()){
	//do something
}

阻塞方法怎么响应中断

  • 对于自定义的可中断的方法,如上,在必要的时候,判断线程中断状态,来响应中断。当然,如果执行的时候,一直没有判断中断状态,自然也不会响应中断。
  • java封装的可中断方法,sleep等,是由JVM来控制的。

一些不会抛出 InterruptedException 的线程阻塞操作

然而,对于某些线程阻塞操作,JVM并不会自动抛出InterruptedException异常。例如,某些I/O操作和内部锁操作。对于这类操作,可以用其他方式模拟中断:

  • java.io中的同步IO
    读写socket的时候,InputStream和OutputStream的read和write方法会阻塞等待,但不会响应java中断。不过,调用Socket的close方法后,被阻塞线程会抛出SocketException异常。
  • 利用Selector实现的异步I/O
    如果线程被阻塞于Selector.select(在java.nio.channels中),调用wakeup方法会引起ClosedSelectorException异常。
  • 等待获取某个内置锁
    如果线程在等待获取一个内部锁,我们将无法中断它。但是,利用Lock类的lockInterruptibly方法,我们可以在等待锁的同时,提供中断能力。

实战

考虑以下场景,使用线程池执行任务,任务包含一系列查询操作和一个修改操作,查询操作可能超时。每个任务的超时时间是3秒。如果FutureTask超时返回,而任务还在继续执行,就会造成数据不一致。这时候需要再查询一次任务的结果才知道执行是否成功。而如果超时之后直接查询,有可能任务还在执行中,这时查到的也是不一样的。此时,可以任务外超时处理中,取消任务;同时,任务内在合适的部分,添加是否中断的判断。如:

//任务外超时处理中,取消任务
try{
    SaveResult result = task.get(3000, TimeUnit.MILLISECONDS));  //3s超时
}catch (Exception e){
    if(e instanceof TimeoutException){
        //超时返回,不知道结果,需要取消任务;参数true表示中断正在执行的操作
        task.cancel(true);
    }
} 

//任务内在合适的部分,添加中断的判断和相关操作
Future<SaveResult> task = executor.submit(new Callable<SaveResult>() {
    @Override
    public SaveResult call() throws Exception {
        //do something, 1.5s
        //do others, 1.5s
        //任务内,判断当前线程是否被中断,如果已经被中断,则不执行变更操作
        if(!Thread.currentThread.isInterruptrf()){
        	//update sth
        }
        return result ;
    }
});

看看FutureTask中cancel源码,如果需要中断,则是使用线程中断的方式

public boolean cancel(boolean mayInterruptIfRunning) {
    if (state != NEW)
        return false;
    if (mayInterruptIfRunning) {
        if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, INTERRUPTING))
            return false;
        Thread t = runner;
        if (t != null)
            t.interrupt();
        UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); // final state
    }
    else if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED))
        return false;
    finishCompletion();
    return true;
}

参考

Java并发:终止线程和关闭线程池

java线程中断

posted on 2017-06-16 15:06  漫夭  阅读(574)  评论(0编辑  收藏  举报