前言
当提及如何终止一个线程时,部分读者通常立马想到的方法肯定是stop(),但是stop()方法并不被推荐使用(很多规范中是禁止使用的),其原因是强制终止一个线程,会导致程序不正常的结束,会出现资源未正确释放、程序结果不正确等等问题。而是否终止一个线程应该把这个控制权转交给当前被终止的线程本身,此时采用的办法就是 ****interrupt()方法来终止,该方法相当于修改一个共享变量的值,当运行中的线程判断当前值为false则继续运行,如果有地方调用当前thread的interrupt()方法,那么这个值将变为true,此时当前线程可以根据这个值的修改来正确的终止线程的运行。
API
在java.lang.Thread中主要提供了如下与线程中断相关的方法,其具体方法名与主要作用如下表所示。
源码
/***中断此线程*/publicvoidinterrupt(){if(this!=Thread.currentThread())checkAccess();synchronized(blockerLock){Interruptibleb=blocker;if(b!=null){interrupt0();//Justtosettheinterruptflagb.interrupt(this);return;}}interrupt0();}/***测试当前线程是否被中断,返回中断标志*/publicstaticbooleaninterrupted(){returncurrentThread().isInterrupted(true);}/***测试当前线程是否被中断,返回中断标志*/publicbooleanisInterrupted(){returnisInterrupted(false);}/***线程是否被中断native方法,ClearInterrupted为是否清除中断标志参数*/privatenativebooleanisInterrupted(booleanClearInterrupted);/***中断当前线程的native方法*/privatenativevoidinterrupt0();
interrupted()和isInterrupted()区别
看了上述API讲述和Thread中的源码,已经清楚interrupted()和isInterrupted()的主要区别了
interrupted()为静态方法,isInterrupted()为普通方法
interrupted() 返回中断标志且清除(恢复)中断标志,isInterrupted()仅返回中断标志
使用方法
我们先验证中断异常响应,通过如下两种方法的使用示例来介绍,注意Runner中的run方法的部分区别
方法一
packagecom.liziba.p7;importjava.util.concurrent.TimeUnit;/***<p>**</p>**@Author:Liziba*@Date:2021/6/2421:05*/publicclassThreadInterruptedDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt1=newThread(newRunner(),"Thread-01");t1.start();//主线程睡眠1秒,保证t1的充分执行TimeUnit.SECONDS.sleep(1);//发起中断t1.interrupt();}staticclassRunnerimplementsRunnable{@Overridepublicvoidrun(){while(!Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+"isrunning.");}}}}
输出结果
可以看到线程在执行数次后终止运行
方法二
packagecom.liziba.p7;importjava.util.concurrent.TimeUnit;/***<p>**</p>**@Author:Liziba*@Date:2021/6/2421:18*/publicclassThreadInterruptedDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt1=newThread(newRunner(),"Thread-01");t1.start();//主线程睡眠2秒,保证t1的充分执行TimeUnit.SECONDS.sleep(1);//发起中断t1.interrupt();}staticclassRunnerimplementsRunnable{@Overridepublicvoidrun(){while(!Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+"isrunning.");try{//睡眠2秒,保证主线程发起的中断能被捕获TimeUnit.SECONDS.sleep(2);}catch(InterruptedExceptione){//不对中断做任何处理,try住异常,打印e.printStackTrace();}}}}}
输出结果
可以看到main线程中发起的t1线程中断,被捕获住异常后,未做任何处理,线程继续持续不断的运行
总结上述两种方式
方法一和方法二,均通过判断Thread.currentThread().isInterrupted()的值来运行run方法中的逻辑,Thread.currentThread().isInterrupted()在线程未中断时返回false,当main线程中执行 t1.interrupt()时,线程t1被中断,Thread.currentThread().isInterrupted()的值变为false;在方法一中,获取到这个变化后直接结束运行;在方法二中,由于sleep()使得线程阻塞会响应中断,但是此时我仅仅catch住异常,并没有对中断做任何处理,这里有个知识点是,线程响应中断抛出异常时,会恢复(清除)中断标志,所以t1.interrupt()对中断标志的修改又被恢复了,程序仍然不断的运行。
接下来我们来验证interrupted()对于中断的标志的清除
packagecom.liziba.p7;importjava.util.concurrent.TimeUnit;/***<p>*isInterrupted()*</p>**@Author:Liziba*@Date:2021/6/2421:20*/publicclassThreadInterruptDemo2{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(newRunner(),"Thread-1");thread.start();TimeUnit.SECONDS.sleep(2);thread.interrupt();}staticclassRunnerimplementsRunnable{@Overridepublicvoidrun(){System.out.println(Thread.currentThread().getName()+"interruptedflagis"+Thread.currentThread().isInterrupted());while(!Thread.currentThread().isInterrupted()){try{System.out.println(Thread.currentThread().getName()+"isrunning.");TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){//响应中断,抛出异常后中断位置会被复位,自己中断自己Thread.currentThread().interrupt();//这里调用isInterrupted()获取当前的中断标志System.out.println(Thread.currentThread().getName()+"interruptedflagis"+Thread.currentThread().isInterrupted());}}}}}
输出结果
这里证明interrupted()不清楚中断标志,线程在获取到 thread.interrupt()发起中断后,执行结束。
将上述catch中的Thread.currentThread().isInterrupted()修改为Thread.interrupted()再次运行
packagecom.liziba.p7;importjava.util.concurrent.TimeUnit;/***<p>**</p>**@Author:Liziba*@Date:2021/6/2421:23*/publicclassThreadInterruptDemo2{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(newRunner(),"Thread-1");thread.start();TimeUnit.SECONDS.sleep(2);thread.interrupt();}//区别在catch中staticclassRunnerimplementsRunnable{@Overridepublicvoidrun(){System.out.println(Thread.currentThread().getName()+"interruptedflagis"+Thread.currentThread().isInterrupted());while(!Thread.currentThread().isInterrupted()){try{System.out.println(Thread.currentThread().getName()+"isrunning.");TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){//响应中断,抛出异常后中断位置会被复位,自己中断自己Thread.currentThread().interrupt();//注意区别在这里System.out.println(Thread.currentThread().getName()+"interruptedflagis"+Thread.interrupted());}}}}}
输出结果
线程也响应到了 thread.interrupt()的中断,但是由于catch中调用了Thread.interrupted(),对中断标志进行了清除,所以!Thread.currentThread().isInterrupted()判断仍然等于true,线程继续不断的运行
看到这里,应该已经理解了这两个方法的主要区别和其使用,最后我们来看下一个源码中的使用案例。我们通过观看AbstractQueuedSynchronizer(AQS)中的await()方法,来看其在源码中的使用。
publicfinalvoidawait()throwsInterruptedException{//判断当前线程是否被中断,如果被中断则恢复中断标志if(Thread.interrupted())thrownewInterruptedException();Nodenode=addConditionWaiter();intsavedState=fullyRelease(node);intinterruptMode=0;while(!isOnSyncQueue(node)){LockSupport.park(this);if((interruptMode=checkInterruptWhileWaiting(node))!=0)break;}if(acquireQueued(node,savedState)&&interruptMode!=THROW_IE)interruptMode=REINTERRUPT;if(node.nextWaiter!=null)//cleanupifcancelledunlinkCancelledWaiters();if(interruptMode!=0)reportInterruptAfterWait(interruptMode);}
AbstractQueuedSynchronizer(AQS)源码中使用静态Thread.interrupted(),判断当前线程是否被中断,并恢复中断标志,如果线程已被中断则抛出InterruptedException中断异常。清除标志位的作用就是为了当前线程响应过中断后,再次进入的时候可以进行后续操作。
作者:李子捌