当前位置: 首页 > 科技观察

Java线程中断机制深入剖析

时间:2023-03-21 18:28:41 科技观察

Thread.interrupt真正可以中断线程在平时的开发过程中,相信多线程都会用到。在使用多线程的时候,也会遇到各种各样的问题。今天我们就来说说一个多线程的问题——线程中断。在java中启动一个线程是非常容易的。大多数情况下,我让一个线程执行自己的任务,然后自己停止,但有时我们需要取消一个操作。例如,当您从Internet下载时,有时您需要取消下载。实现线程的安全中断并不是一件容易的事,因为Java不支持安全快速中断线程的机制。估计很多同学会说java没有提供中断线程的Thread.interrupt方法。那么,我们今天就从这个方法入手。但是调用这个方法的线程真的停止了吗?写个demo看看吧。publicclassMain{privatestaticfinalStringTAG="Main";publicstaticvoidmain(String[]args){Threadt=newThread(newNRunnable());t.start();System.out.println("isstart......");尝试{Thread.sleep(3000);}catch(InterruptedExceptione){}t.interrupt();System.out.println("isinterrupt......");}publicstaticclassNRunnableimplementsRunnable{@Overridepublicvoidrun(){while(true){System.out.println("Idon'thaveaninterrupt");try{Thread.sleep(1000);}catch(InterruptedExceptione){}}}}}如果interruptp方法可以中断线程,那么isinterrupt.......后面应该没有log了,看看执行结果是start......我没有中断我没有中断我没有一个中断...我没有任何中断我没有任何中断我没有任何中断它只是告诉线程外面有中断请求,是否是中断取决于线程本身。Thread类中除了interrupt()方法外,还有另外两个非常相似的方法:interrupted和isInterrupted方法。下面分别解释一下这几个方法:interrupt这个方法是一个实例方法,用来告诉这个线程有外部中断请求,并且把线程中的中断标志设置为trueinterrupted这个方法是类方法,测试是否有中断当前线程已被中断。该方法清除线程的中断状态。也就是说,如果连续调用该方法两次,第二次调用将返回false(第一次调用清除其中断状态后,第二次调用检查中断状态之前,当前线程再次被中断,除了caseofisInterrupted该方法是一个实例方法,用于测试线程是否被中断,线程的中断状态不受该方法的影响。忽略线程中断,因为中断时不活跃的线程会通过返回false的方法体现){NRunnablerun=newNRunnable();Threadt=newThread(run);t.start();System.out。println("isstart......");try{Thread.sleep(3000);}catch(InterruptedExceptione){}run.cancel();System.out.println("cancel..."+System.currentTimeMillis());}publicstaticclassNRunnableimplementsRunnable{publicbooleanisCancel=false;@Overridepublicvoidrun(){while(!isCancel){System.out.println("我没有中断");try{Thread.sleep(10000);}catch(InterruptedExceptione){}}System.out.println("Ihaveended..."+System.currentTimeMillis());}publicvoidcancel(){this.isCancel=true;}}执行结果如下:是start。......Ididn'tinterruptcancel...1438396915809Ihaveended...1438396922809通过结果,我们发现线程确实被中断了,但是细心的同学应该发现了问题。从调用cancel方法到执行***线程之间有好几秒的间隔,也就是说线程并没有立即中断。下面我们来分析一下原因。:子线程退出的条件是while循环结束,即cancel标志置为true,但是当我们调用cancel方法将calcel标志置为true时,有一个耗时操作在while循环中(sleep方法模拟),只等待操作完成后面会检查这个标志,所以cancel方法和线程退出之间有一个时间间隔通过interrupt和isinterrupt方法中断线程publicstaticvoidmain(String[]args){Threadt=newNThread();t.start();System.out.println("isstart......");try{Thread.sleep(3000);}catch(InterruptedExceptione){}System.out.println("startinterrupt..."+System.currentTimeMillis());t.interrupt();System.out.println("endinterrupt..."+System.currentTimeMillis());}publicstaticclassNThreadextendsThread{@Overridepublicvoidrun(){while(!this.isInterrupted()){System.out.println("我没有中断");try{Thread.sleep(10000);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}System.out.println("I'mover..."+System.currentTimeMillis());}}}运行结果为如下:是开始。...我没有打断startinterrupt...1438398800110我已经结束了...1438398800110endinterrupt...1438398800110这次是马上打断了,但是这个方法有局限性,这个方法只对会的任务有效抛出InterruptedException异常,比如java中的sleep、wait等方法。对于不会抛出此类异常的任务,效果其实和第一种方法一样,都会有延迟。这个例子中另一个非常重要的一点是,在缓存语句中,我们调用了Thread.currentThread().interrupt()并且我们把去掉这段代码,运行一下会发现线程无法终止,因为在抛出InterruptedException时线程的中断标志位被清除,所以在while语句中判断当前线程是否被中断时,返回false。对于InterruptedException,我想说的是:你不能在catch语句块中做任何事情。如果实在不想处理,可以抛异常,让调用抛异常的方法变成可以抛InterruptedException的方法。如果你要捕获这个异常,那么在缓存语句中***调用Thread.currentThread().interrupt();让高层中断请求和处理中断的方法对于以上两种方法都有其局限性,***该方法只能处理工作量小且经常检查循环标志的任务。对于第二种方法,适用于抛出InterruptedException异常的代码。也就是说,第一种和第二种方法都支持支持中断的线程任务,那么不支持中断的线程任务怎么办。比如一个线程因为同步I/O操作而阻塞,中断请求不会抛出InterruptedException ,我们如何中断这个线程。不支持中断处理线程中断的常用方法重写线程的中断方法publicstaticclassReaderThreadextendsThread{publicstaticfinalintBUFFER_SIZE=512;Socketsocket;InputStreamis;publicReaderThread(Socketsocket)throwsIOException{this.socket=socket;is=this.socket.getInputinterpubstream();}@Oidlic(){try{socket.close();}catch(IOExceptione){}finally{super.interrupt();}super.interrupt();}@Overridepublicvoidrun(){try{byte[]buf=newbyte[BUFFER_SIZE];while(true){intcount=is.read(buf);if(count<0)break;elseif(count>0){}}}catch(IOExceptione){}}}}比如上面的例子,改写了Thread的中断方法。当中断方法被调用时,套接字将被关闭。如果此时read方法被阻塞,则会抛出IOException异常,线程任务结束。上面的方法是通过重写线程的中断方法实现的,那么如何使用线程池中断任务呢。重写线程池的newTaskFor方法。通常我们以如下形式向线程池中添加一个任务:Futurefuture=executor.submit(newRunnable(){@Overridepublicvoidrun(){}});在取消任务的时候,future调用了cancel方法,其实就是在cancel方法中调用了线程的interrupt方法。所以对于不支持中断的任务,cancel也是无效的。让我们看一下提交方法。publicFuturesubmit(Runnabletask){if(task==null)thrownewNullPointerException();RunnableFutureftask=newTaskFor(task,null);execute(ftask);returnftask;}这里调用了AbstractExecutorService的newTaskFor方法,那么是否可以重写ThreadPoolExecutor的newTaskFor方法呢?接下来,让我们看看我在做什么。定义一个基类,所有需要取消的任务继承这个基类publicinterfaceCancelableRunnableextendsRunnable{publicvoidcancel();publicRunnableFuturenewTask();}把上面的ReaderThread改成继承这个类publicstaticclassReaderThreadimplementsCancelableRunnable{publicstaticfinalintBUFFER_SIZE=512;Socketsocketsami;InputStrelic)throwsIOException{this.socket=socket;is=this.socket.getInputStream();}@Overridepublicvoidrun(){try{byte[]buf=newbyte[BUFFER_SIZE];while(true){intcount=is.read(buf);if(count<0)break;elseif(count>0){}}}catch(IOExceptione){}}@Overridepublicvoidcancel(){try{socket.close();}catch(IOExceptione){}}@OverridepublicRunnableFuturenewTask(){returnnewFutureTask(this,null){@Overridepublicbooleancancel(booleanmayInterruptIfRunning){returnsuper.cancel(mayInterruptIfRunning);if(ReaderThread.thisinstanceofCancelableRunnable)){((CancelableRunnable)(ReaderThread.this)).cancel();}else{super.cancel(mayInterruptIfRunning);}}};}}调用future的cancel方法时,会关闭socket,最终导致read方法异常,从而终止线程任务