正确结束Java线程的方法
使用标志位
很简单地设置一个标志位,名称就叫做isCancelled。启动线程后,定期检查这个标志位。如果isCancelled=true,那么线程就马上结束。
publicclassMyThreadimplementsRunnable{ privatevolatilebooleanisCancelled; publicvoidrun(){ while(!isCancelled){ //dosomething } } publicvoidcancel(){isCancelled=true;} }
注意的是,isCancelled需要为volatile,保证线程读取时isCancelled是最新数据。
我以前经常用这种简单方法,在大多时候也很有效,但并不完善。考虑下,如果线程执行的方法被阻塞,那么如何执行isCancelled的检查呢?线程有可能永远不会去检查标志位,也就卡住了。
使用中断
Java提供了中断机制,Thread类下有三个重要方法。
- publicvoidinterrupt()
- publicbooleanisInterrupted()
- publicstaticbooleaninterrupted();//清除中断标志,并返回原状态
每个线程都有个boolean类型的中断状态。当使用Thread的interrupt()方法时,线程的中断状态会被设置为true。
下面的例子启动了一个线程,循环执行打印一些信息。使用isInterrupted()方法判断线程是否被中断,如果是就结束线程。
publicclassInterruptedExample{ publicstaticvoidmain(String[]args)throwsException{ InterruptedExampleinterruptedExample=newInterruptedExample(); interruptedExample.start(); } publicvoidstart(){ MyThreadmyThread=newMyThread(); myThread.start(); try{ Thread.sleep(3000); myThread.cancel(); }catch(InterruptedExceptione){ e.printStackTrace(); } } privateclassMyThreadextendsThread{ @Override publicvoidrun(){ while(!Thread.currentThread().isInterrupted()){ try{ System.out.println("test"); Thread.sleep(1000); }catch(InterruptedExceptione){ System.out.println("interrupt"); //抛出InterruptedException后中断标志被清除,标准做法是再次调用interrupt恢复中断 Thread.currentThread().interrupt(); } } System.out.println("stop"); } publicvoidcancel(){ interrupt(); } } }
对线程调用interrupt()方法,不会真正中断正在运行的线程,只是发出一个请求,由线程在合适时候结束自己。
例如Thread.sleep这个阻塞方法,接收到中断请求,会抛出InterruptedException,让上层代码处理。这个时候,你可以什么都不做,但等于吞掉了中断。因为抛出InterruptedException后,中断标记会被重新设置为false!看sleep()的注释,也强调了这点。
@throwsInterruptedException ifanythreadhasinterruptedthecurrentthread. Theinterruptedstatusofthecurrentthreadis clearedwhenthisexceptionisthrown. publicstaticnativevoidsleep(longmillis)throwsInterruptedException;
记得这个规则:什么时候都不应该吞掉中断!每个线程都应该有合适的方法响应中断!
所以在InterruptedExample例子里,在接收到中断请求时,标准做法是执行Thread.currentThread().interrupt()恢复中断,让线程退出。
从另一方面谈起,你不能吞掉中断,也不能中断你不熟悉的线程。如果线程没有响应中断的方法,你无论调用多少次interrupt()方法,也像泥牛入海。
用Java库的方法比自己写的要好
自己手动调用interrupt()方法来中断程序,OK。但是Java库提供了一些类来实现中断,更好更强大。
Executor框架提供了Java线程池的能力,ExecutorService扩展了Executor,提供了管理线程生命周期的关键能力。其中,ExecutorService.submit返回了Future对象来描述一个线程任务,它有一个cancel()方法。
下面的例子扩展了上面的InterruptedExample,要求线程在限定时间内得到结果,否则触发超时停止。
publicclassInterruptByFuture{ publicstaticvoidmain(String[]args)throwsException{ ExecutorServicees=Executors.newSingleThreadExecutor(); Future>task=es.submit(newMyThread()); try{ //限定时间获取结果 task.get(5,TimeUnit.SECONDS); }catch(TimeoutExceptione){ //超时触发线程中止 System.out.println("threadovertime"); }catch(ExecutionExceptione){ throwe; }finally{ booleanmayInterruptIfRunning=true; task.cancel(mayInterruptIfRunning); } } privatestaticclassMyThreadextendsThread{ @Override publicvoidrun(){ while(!Thread.currentThread().isInterrupted()){ try{ System.out.println("count"); Thread.sleep(1000); }catch(InterruptedExceptione){ System.out.println("interrupt"); Thread.currentThread().interrupt(); } } System.out.println("threadstop"); } publicvoidcancel(){ interrupt(); } } }
Future的get方法可以传入时间,如果限定时间内没有得到结果,将会抛出TimeoutException。此时,可以调用Future的cancel()方法,对任务所在线程发出中断请求。
cancel()有个参数mayInterruptIfRunning,表示任务是否能够接收到中断。
- mayInterruptIfRunning=true时,任务如果在某个线程中运行,那么这个线程能够被中断;
- mayInterruptIfRunning=false时,任务如果还未启动,就不要运行它,应用于不处理中断的任务
要注意,mayInterruptIfRunning=true表示线程能接收中断,但线程是否实现了中断不得而知。线程要正确响应中断,才能真正被cancel。
线程池的shutdownNow()会尝试停止池内所有在执行的线程,原理也是发出中断请求。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。