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

想打断线程不打断

时间:2023-03-22 14:09:35 科技观察

本文转载自微信公众号《JerryCodes》,作者KyleJerry。转载本文请联系JerryCodes公众号。为什么不强行停止如何使用中断来停止线程你能感觉到睡眠中的中断吗?启动一个线程很容易,但要正确地停止它就没那么容易了。线程的几种实现方式,可以看出我上一篇文章中实现线程的方式本质上只有一种。为什么不强制停止?对于Java来说,停止线程最正确的方式就是使用interrupt。但是中断只是起到了通知被停止线程的作用。至于被停止的线程,它有完全的自主权。它可以选择立即停止,一段时间后停止,或者选择根本不停止。为什么Java不提供强制停止线程的能力?其实Java希望程序之间可以相互通知,协同管理线程,因为如果不知道对方在做什么,强行停止线程可能会造成一些安全问题。例如:一个线程正在写一个文件,当它收到一个终止信号时,需要根据自己的业务来判断,是选择立即停止,还是写完整个文件成功后停止。如果选择立即停止,数据可能不完整,中断命令的发起者和接收者都不希望数据出现问题。如何用中断停止线程while(!Thread.currentThread().isInterrupted()&&moreworktodo){domorework}一旦我们调用了某个线程的interrupt(),这个线程的中断标志位就会被置为true。每个线程都有这样一个标志位。当线程正在执行时,应该定期检查这个标志位。如果标志位设置为真,则意味着程序要终止线程。回到源码,可以看到在while循环体的判断语句中,首先通过Thread.currentThread().isInterrupt()判断线程是否被中断,然后检查是否还有工作要做.&&逻辑是指只有同时满足两个判断条件,才会进行后面的工作。publicclassStopThreadimplementsRunnable{@Overridepublicvoidrun(){intcount=0;while(!Thread.currentThread().isInterrupted()&&count<1000){System.out.println("count="+count++);}}publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(newStopThread());thread.start();Thread.sleep(5);thread.interrupt();}}在StopThread类的run()方法中,首先判断线程是否是打断,然后判断计数值是否小于1000。这个线程的工作内容很简单,就是打印0到999之间的数字,每打印一个数字,计数值就加1。可以看出,线程在每次循环开始前都会检查自己是否被中断。接下来会在main函数中启动线程,然后线程休眠5毫秒后立即中断。该线程会检测到中断信号,因此它会在打印完1000个数字之前停止。线程正确停止的情况。能不能感觉到睡眠中的打扰首先是结论,是的。publicclassStopDuringSleep{publicstaticvoidmain(String[]args)throwsInterruptedException{Runnablerunnable=()->{intnum=0;try{while(!Thread.currentThread().isInterrupted()&&num<=1000){System.out.println(num);num++;Thread.sleep(1000000);}}catch(InterruptedExceptione){e.printStackTrace();}};Threadthread=newThread(runnable);thread.start();Thread.sleep(5);thread.interrupt();}}运行后的结果,猜猜看,程序会抛出异常。如果sleep,wait等可以让线程进入阻塞方法让线程休眠,休眠的线程被中断,那么线程就能感受到中断信号,就会抛出InterruptedException,中断信号就会同时清零,中断标志位将被置为假。这样就不用担心线程在长时间休眠期间感受不到中断了,因为即使线程还在休眠,它仍然可以响应中断通知并抛出异常。但是这样只能响应一次中断信号,怎么办?我的生意还没做完,怎么办?try/catch的合理使用我们在实际开发中不能盲目吞掉中断,如果没有在方法签名中声明,它是不会在catch语句块中再次恢复中断,而是在catch中不进行处理.我们称这种行为为“屏蔽中断请求”。如果我们一味地阻塞中断请求,中断信号将被完全忽略,最终线程将无法正确停止。try{Thread.sleep(2000);}catch(InterruptedExceptione){//这里处理中断异常请求,业务结束}停止线程有几种方式可运行>立即关闭;下面我们将这些方法一一展开。shutdown()调用shutdown()方法后,并没有立即关闭线程池,因为此时线程池中可能还有很多任务正在执行,或者线程池中有大量任务等待执行任务队列,调用shutdown()方法,线程池在执行完正在执行的任务和队列中等待的任务后会完全关闭。但这并不意味着shutdown()操作没有效果。如果调用shutdown()方法后有新的任务提交,线程池会根据拒绝策略直接拒绝后续新提交的任务。isShutdown()可以返回true或false来判断线程池是否已经开始关闭工作,即是否执行了shutdown或shutdownNow方法。这里需要注意的是,如果调用isShutdown()方法返回的结果为true,并不代表此时线程池已经完全关闭,只是代表线程池开始了关闭过程,即也就是说,可能还有正在执行任务的线程,队列中也可能还有等待执行的任务。isTerminated()方法可以检测线程池是否真的“终止”了,这不仅仅意味着线程池关闭了,还意味着线程池中的所有任务都已经执行完了,因为我们刚才说了调用shutdown方法,线程池会继续执行其中未完成的任务,不仅包括线程正在执行的任务,还包括任务队列中等待的任务。比如此时已经调用了shutdown方法,但是一个线程还在执行任务,那么此时调用isShutdown方法返回true,调用isTerminated方法返回false,因为线程中还有任务在执行pool,线程池并没有真正“完成”。直到所有任务都执行完毕,调用isTerminated()方法会返回true,表示线程池关闭,线程池为空,剩余任务全部执行完毕。awaitTermination()的第四个方法称为awaitTermination()。它不是用来关闭线程池本身的,主要是用来判断线程池的状态。例如,如果我们将10秒传入awaitTermination方法,它会等待10秒,直到出现以下三种情况之一:等待期间(包括进入等待状态之前),线程池关闭,所有提交的任务(包括正在执行的和在队列中等待的任务)全部执行完,这意味着线程池已经“终止”,该方法将返回true。等待超时后,第一个线程池“终止”的情况一直没有发生,方法返回false并且在等待期间线程被中断,方法会抛出InterruptedException异常。在等待期间(包括进入等待状态之前),线程池关闭,所有提交的任务(包括正在执行的和在队列中等待的)都已经执行完毕。相当于线程池已经“终止”了,方法会返回true;等待超时后,第一个线程池“终止”从未发生,方法返回false;线程在等待期间被中断,方法会抛出InterruptedException异常。shutdownNow()最后一个方法是shutdownNow(),也是五个方法中最强大的一个。它和第一种关机方式的区别在于名称中多了一个Now字样,意思是立即关闭的意思。shutdownNow方法执行后,会向线程池中的所有线程发送中断中断信号,试图中断这些任务的执行,然后将任务队列中等待的所有任务转移到一个List中返回。我们可以根据返回的任务列表进行一些补救操作,比如记录日志,稍后重试。publicListshutdownNow(){Listtasks;finalReentrantLockmainLock=this.mainLock;mainLock.lock();try{checkShutdownAccess();advanceRunState(STOP);interruptWorkers();tasks=drainQueue();}finally{mainLock.unlock();}tryTerminate();returntasks;}源码中有一行interruptWorkers()代码,这行代码会中断每一个已经启动的线程,让线程检测到中断信号在任务执行过程中,进行相应的处理,提前结束任务。这里需要注意的是,由于Java中强制停止线程机制的限制,即使我们调用了shutdownNow方法,如果被中断的线程忽略了中断信号,仍然有可能导致任务无法停止。总结中断和关闭线程的方法有很多种,看起来很相似,但实际上有很多方法。如果处理不好,会导致程序崩溃。