详解Java 线程中断
一、前言
大家肯定都使用过Java线程开发(Thread/Runnable),启动一个线程的做法通常是:
newThread(newRunnable( @Override publicvoidrun(){ //todosth... } )).start();
然而线程退出,大家是如何做的呢?一般做法可能不外乎以下两种:
- 设置一个标志位:true/false来退出;
- 强制退出:thread.stop;(我相信,现在应该没人会使用这种方式了,因为JDK也很早就废弃了该方法)
可能还会有人提出,我可以用中断来退出线程!我只能说:TooYoungTooSimple!中断并不会使得线程结束而退出,中断(interrupt)只是唤醒被阻塞的线程而已。
本篇,我们就来好好的聊聊:线程中断,以及如何正确的使用线程中断,和正确的线程退出。
二、为何Thread.stop被废弃
Thismethodisinherentlyunsafe.StoppingathreadwithThread.stopcausesittounlockallofthemonitorsthatit haslocked(asanaturalconsequenceoftheuncheckedThreadDeathexceptionpropagatingupthestack).Ifanyofthe objectspreviouslyprotectedbythesemonitorswereinaninconsistentstate,thedamagedobjectsbecomevisibleto otherthreads,potentiallyresultinginarbitrarybehavior.Manyusesofstopshouldbereplacedbycodethatsimply modifiessomevariabletoindicatethatthetargetthreadshouldstoprunning.Thetargetthreadshouldcheckthis variableregularly,andreturnfromitsrunmethodinanorderlyfashionifthevariableindicatesthatitistostop running.Ifthetargetthreadwaitsforlongperiods(onaconditionvariable,forexample),theinterruptmethod shouldbeusedtointerruptthewait.
以上是官方JDK中的源码注释说明,其含义如下:
**Thread.stop方法天生就不安全。**使用该方法来停止线程,将会导致其它因为监视器锁『监视器我们在synchronized中就讲过,是Java的内置锁』而被锁住的线程全部都解锁!(本质的后果是:没有检查的ThreadDeath异常会在栈中传播,因而使得监视器锁解锁)。如果任何一个被监视器锁给锁住的对象处于一个不一致的状态,那么其被解锁后将会被其它线程可见,潜在的结果是产生任何后果。**我们应该使用一个变量来代替使用stop方法,告诉目标线程退出『这里就是我们开头所说的第一种方法,设置一个标志位』。**目标线程应该周期性的检查这个变量,并根据这个变量来正确的退出run方法。如果目标线程处于阻塞/休眠状态(如:使用wait、sleep、yield方法后,线程让出了CPU使用权,进而阻塞/休眠),此时,该标志位变量将不会起作用,那么,应该使用interrupt方法来中断目标线程的阻塞/休眠状态,将其唤醒!
对于ThreadDeath对象,官方还有补充:
- 线程可以在几乎任何地方抛出ThreadDeath异常。由于这一点,所有的同步方法和(代码)块将必须被考虑得事无巨细。
- 线程在清理第一个ThreadDeath异常的时候(在catch或finally语句中),可能会抛出第二个。清理工作将不得不重复直到到其成功。保障这一点的代码将会很复杂。
所以,我们也别想着去try-catchThreadDeathException!
同样,被废弃的还有Thread.resume和Thread.suspend。这俩方法有造成死锁的危险:
- 使用suspend时,并不会释放锁;
- 如果存在某种情况要先获取该锁,再进行resume,那么就造成死锁了;
取代这两方法的正确方式是:Object.wait和Object.notify:
因为Object.wait进入阻塞时,会释放锁。
三、线程中断的含义
Thread中有三个与中断相关的方法:
- 成员方法interrupt():设置线程中断标志为true;
- 成员方法isInterrupted():获取线程的中断状态,默认为false,调用interrupt()后,该方法返回true;
- 静态方法Thread.interrupted():获取线程的中断状态,并且清除中断状态(设置为false);
注:如果线程中断后,连续两次调用Thread.interrupted(),第一次是true&清除状态,第二次结果是false。
3.1、初步了解
我们先来通过一个例子来初步了解thread.interrupt:
publicclassInterruptDemoimplementsRunnable{ @Override publicvoidrun(){ while(true){ System.out.println("Threadrunning..."); } } publicstaticvoidmain(String[]args)throwsInterruptedException{ Threadthread=newThread(newInterruptDemo(),"InterruptDemo"); System.out.println("startthread"); thread.start(); Thread.sleep(50); System.out.println("interruptthread"); thread.interrupt(); Thread.sleep(50); System.out.println("thread'sstatus="+thread.isInterrupted()); } }
输出结果:
startthread Threadrunning... Threadrunning... ...... interruptthread Threadrunning... Threadrunning... ...... thread'sstatus=true Threadrunning... ......
我们可以看到,即便我们调用了thread.interrupt方法,线程也并没有退出,仍旧继续运行。因此,这个例子证明了一点:我们并不能通过"我们所认为的"中断来试图"结束"正在运行的线程。
3.2、中断即唤醒阻塞/休眠的线程
同样,我们再来看一个例子:
publicclassInterruptDemoimplementsRunnable{ @Override publicvoidrun(){ while(true){ System.out.println("Threadwillsleep10s-------------------------running"); longtimestamp=System.currentTimeMillis(); try{ Thread.sleep(10000); }catch(InterruptedExceptione){ System.out.println("threadinterrupted..."); } timestamp=System.currentTimeMillis()-timestamp; System.out.println("Threadrun,totalsleep="+timestamp+"(ms)"); } } publicstaticvoidmain(String[]args)throwsInterruptedException{ Threadthread=newThread(newInterruptDemo(),"InterruptDemo"); System.out.println("startthread"); thread.start(); Thread.sleep(3000); System.out.println("interruptthread"); thread.interrupt(); System.out.println("mainexit"); } }
输出结果:
startthread Threadwillsleep10s-------------------------running interruptthread mainexit threadinterrupted... Threadrun,totalsleep=3002(ms) Threadwillsleep10s-------------------------running Threadrun,totalsleep=10002(ms) Threadwillsleep10s-------------------------running
我们可以看到,线程启动后,进入睡眠(10s),3秒后被中断唤醒,执行完一个while后再次进入第二次睡眠(10s),然后周而复始。
3.3、一般标志位法退出线程
publicclassInterruptDemoimplementsRunnable{ privatestaticfinalAtomicBooleanrunning=newAtomicBoolean(true); @Override publicvoidrun(){ while(running.get()){ longtimestamp=System.currentTimeMillis(); timestamp=System.currentTimeMillis()-timestamp; System.out.println("Threadrun,totalsleep="+timestamp+"(ms)"); } System.out.println("Threadexit"); } publicstaticvoidmain(String[]args)throwsInterruptedException{ Threadthread=newThread(newInterruptDemo(),"InterruptDemo"); System.out.println("startthread"); thread.start(); Thread.sleep(100); System.out.println("interruptthread"); thread.interrupt(); running.set(false); System.out.println("mainexit"); } }
输出结果:
startthread ....... Threadrun,totalsleep=0(ms) interruptthread Threadrun,totalsleep=0(ms) Threadrun,totalsleep=0(ms) Threadrun,totalsleep=0(ms) mainexit Threadexit
我们通过使用一个AtomicBoolean变量来当作标志位,使得我们的线程能正常退出。我们也可以判断线程是否被中断而选择性的退出。
3.4、线程中断退出
publicclassInterruptDemoimplementsRunnable{ @Override publicvoidrun(){ while(!Thread.currentThread().isInterrupted()){ longtimestamp=System.currentTimeMillis(); timestamp=System.currentTimeMillis()-timestamp; System.out.println("Threadrun,totalsleep="+timestamp+"(ms)"); } System.out.println("Threadexit"); } publicstaticvoidmain(String[]args)throwsInterruptedException{ Threadthread=newThread(newInterruptDemo(),"InterruptDemo"); System.out.println("startthread"); thread.start(); Thread.sleep(100); System.out.println("interruptthread"); thread.interrupt(); System.out.println("mainexit"); } }
输出结果:
startthread ....... Threadrun,totalsleep=0(ms) interruptthread Threadrun,totalsleep=0(ms) Threadrun,totalsleep=0(ms) Threadrun,totalsleep=0(ms) mainexit Threadexit
3.5、标志位+线程中断结合
publicclassInterruptDemoimplementsRunnable{ privatestaticfinalAtomicBooleanrunning=newAtomicBoolean(true); @Override publicvoidrun(){ while(running.get()){ System.out.println("Threadwillsleep10s-------------------------running"); longtimestamp=System.currentTimeMillis(); try{ Thread.sleep(10000); }catch(InterruptedExceptione){ System.out.println("Interrupted...Todootherthingsthenexit......"); running.set(false); continue; } timestamp=System.currentTimeMillis()-timestamp; System.out.println("Threadrun,totalsleep="+timestamp+"(ms)"); } System.out.println("Threadexit"); } publicstaticvoidmain(String[]args)throwsInterruptedException{ Threadthread=newThread(newInterruptDemo(),"InterruptDemo"); System.out.println("startthread"); thread.start(); Thread.sleep(3000); System.out.println("interruptthread"); thread.interrupt(); System.out.println("mainexit"); } }
输出结果:
startthread Threadwillsleep10s-------------------------running interruptthread mainexit Interrupted...Todootherthingsthenexit...... Threadexit
四、总结
本文我们分析了线程的中断,并让大家了解了中断的含义:只是告诉该线程,你被『中断』了,至于你想干嘛,还是由你自己来决定。同时,我们也简单分析了几个废弃的方法的原因。希望大家学习了本文之后,能正确且合理的设计,线程如何安全的退出。
五、附录
- Object.wait:阻塞当前线程,释放持有的锁;
- Object.notify:唤醒当前对象上被阻塞的线程,使其进入就绪状态;
- Object.notifyAll:唤醒所有线程;
- Thread.sleep:指定当前线程休眠一定时间,让出CPU,但不会释放同步资源锁;
- Thread.yield:让出CPU使用权,让自己和其它线程来争夺使用CPU的机会,因此,使用此方法后,并不能保证该线程又再次拿到CPU而恢复运行(使用此方法后,优先级高的线程拿到CPU的概率较大,但优先级低的线程也有概率拿到CPU而执行),同理不会释放同步资源锁;
以上就是详解Java线程中断的详细内容,更多关于Java线程中断的资料请关注毛票票其它相关文章!