浅析Java线程的中断机制
线程中断机制提供了一种方法,用于将线程从阻塞等待中唤醒,尝试打断目标线程的现有处理流程,使之响应新的命令。Java留给开发者这一自由,我们应当予以善用。
今天我们聊聊Java线程的中断机制。
线程中断机制提供了一种方法,有两种常见用途:
将线程从阻塞等待中唤醒,并作出相应的“受控中断”处理。
尝试告知目标线程:请打断现有处理流程,响应新的命令。
以第一种用途为例,请看以下代码:
synchronized(lock){
try{
while(!check()){
lock.wait(1000);
}
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
这段代码使用了Java提供的wait/notify机制,线程执行lock.wait()会阻塞,有三种情况使线程恢复运行。
1、超时1000ms结束,正常执行下一句代码。
2、另一个线程执行下述代码主动唤醒
synchronized(lock){
lock.notifyAll();//orlock.notify();
}
这也会正常执行下一句代码。
3、另一个线程要求等待的线程“中断”
//拿到等待中的线程的引用 Threada; a.interrupt();
被“中断”的线程a,会在lock.wait()处抛出InterruptedException异常。
综上所述,你可以认为object.wait()内部在做这些事:
booleancheckTimeout=timeout>0;
Threadcurrent=Thread.currentThread();
lock.addWaiter(current);
while(!current.isNotified()){
if(current.isInterrupted()){
current.clearInterrupted();
thrownewInterruptedException();
}
if(checkTimeout){
if(timeout==0)break;
timeout--;
}
}
这不完全准确,因为wait不使用这种“忙轮询”的方式做检查,但关于标志位的判断逻辑是正确的。
让我们从上文所述的“手动发出中断”这一操作开始探究
//sun.nio.ch.Interruptible
publicinterfaceInterruptible{
voidinterrupt(Threadvar1);
}
//java.lang.Thread
privatevolatileInterruptibleblocker;
privatefinalObjectblockerLock=newObject();
publicvoidinterrupt(){
if(this!=Thread.currentThread())
checkAccess();
synchronized(blockerLock){
Interruptibleb=blocker;
if(b!=null){
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
//Justtosettheinterruptflag
privatenativevoidinterrupt0();
能够看出,thread.interrupt()先判断权限,然后实际调用interrupt0()设置线程的中断标志,如果当前线程有nio的Interruptible那么还会回调它。
注意,interrupt0()只是设置了线程的中断标志。
当一个线程并不阻塞,没有在object.wait(),thread.join(),Thread.sleep()等不受Java程序逻辑控制的区域时,那么会发生什么事情?答案是不会发生任何事情,线程是否被打断只能通过主动地检查中断标志得知。
怎么检查?Thread暴露了两个接口,Thread.interrupted()和thread.isInterrupted()。
//java.lang.Thread
publicstaticbooleaninterrupted(){
returncurrentThread().isInterrupted(true);
}
publicbooleanisInterrupted(){
returnisInterrupted(false);
}
privatenativebooleanisInterrupted(booleanclearInterrupted);
能够看出,两者都是依靠内部的isInterrupted(boolean),而它会返回线程是否被打断,并根据需要清空中断标志。
当一个函数调用会发生阻塞,Java库函数在阻塞的源头签名里标记throwsInterruptedException,并要求编写trycatch处理中断。
当线程发生了阻塞,就像上文所述,Java检查到中断标志,先将其清除,然后抛出InterruptedException。
//java.lang.Object
publicfinalvoidwait()throwsInterruptedException{
wait(0);
}
publicfinalnativevoidwait(longtimeout)throwsInterruptedException;
如果一个线程收到InterruptedException,之后仍然执行了会引发阻塞的代码,它将像“没事人”一样继续阻塞住。因为Java在内部将中断标志清除了!
我们常见地编写以下三类处理InterruptedException的代码:
将InterruptedException交由上层处理。
publicvoidfoo()throwsInterruptedException{
synchronized(lock){
lock.wait();
}
}
遇到InterruptedException重设中断标志位。
try{
synchronized(lock){
lock.wait();
}
}catch(InterruptedExceptione){
Thread.currentThread().interrupt();
//break;
}
先忙完,再重新抛出InterruptedException。
publicvoidbar()throwsInterruptedException{
InterruptedExceptionie=null;
booleandone=false;
while(!done){
synchronized(lock){
try{
lock.wait();
}catch(InterruptedExceptione){
ie=e;
continue;
}
}
done=true;
}
if(ie!=null){
throwie;
}
}
如果一个线程无视中断标志和InterruptedException,它仍然能够跑的很好。但这与我们设计多线程的初衷是违背的,我们希望线程之间是和谐的有序协作以实现特定功能,因此受控线程应当对中断作出响应。而Java留给开发者这一自由,我们应当予以善用。
以上就是这次给大家介绍的Java线程的中断机制相关知识的全部内容,如果还有任何不明白的可以在下方的留言区域讨论,感谢对毛票票的支持。