如何关闭线程池

参考答案

1.  关闭线程池的两个方法

  • shutdownNow:线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行。
  • shuwdown:线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。

2.  两个关闭线程池方法的源码详解

下面结合线程池源码,分别说说这两个线程池关闭方法的一些实现细节。

2.1   shutdownNow

shutdownNow方法的源码:

如何关闭线程池

在shutdownNow方法里,重要的三句代码已用红色数字标识。

  • 第一句,原子性的修改线程池的状态为STOP状态(比较简单我就不贴代码了)
  • 第三句,将队列里还没有执行的任务放到列表里,返回给调用方。
  • 第二句,遍历线程池里的所有工作线程,然后调用线程的interrupt方法。如图:

如何关闭线程池

以上就是shutdownNow方法的执行逻辑:将线程池状态修改为STOP,然后调用线程池里的所有线程的interrupt方法。

调用shutdownNow后,线程池里的线程会做如何反应呢?

那就要看,线程池里线程正在执行的代码逻辑了。

其在线程池的runWorker方法里(对线程池执行原理不了解的,本站www.javamianshi.com搜索“线程池”,看下之前关于线程池的文章),代码如下:

如何关闭线程池

正常情况下,线程池里的线程,就是在这个while循环里不停地执行。

其中,代码task.run()就是在执行我们提交给线程池的任务,当我们调用shutdownNow时,task.run()里面正处于IO阻塞,则会导致报错,如果task.run()里正在正常执行,则不受影响,继续执行完这个任务。

上图可见,如果getTask()方法返回null,也会导致线程的退出。

我们再来看看getTask方法的实现:

如何关闭线程池

如果我们调用shutdownNow方法时,线程处于从队列里读取任务而阻塞中(图中下边的红框),则会导致抛出InterruptedException异常,但因为异常被捕获,线程将会继续在这个for循环里执行。

当执行到上边红框里的代码时,由于STOP状态值是大于SHUTDOWN状态,STOP也大于等于STOP,不管任务队列是否为空,都会进入if语句从而返回null,线程退出。

2.2   总结

当我们调用线程池的shutdownNow时:

  • 如果线程正在getTask方法中执行,则会通过for循环进入到if语句,于是getTask返回null,从而线程退出。不管线程池里是否有未完成的任务。
  • 如果线程因为执行提交到线程池里的任务而处于阻塞状态,则会导致报错(如果任务里没有捕获InterruptedException异常),否则线程会执行完当前任务,然后通过getTask方法返回为null来退出。

 

3.  shutdown

再来看看shutdown方法的源码:

如何关闭线程池

跟shutdownNow类似,只不过它是将线程池的状态修改为SHUTDOWN状态,然后调用interruptIdleWorkers方法,来中断空闲的线程。

这是interruptIdleWorkers方法的实现:

如何关闭线程池

跟shutdownNow方法调用interruptWorkers方法不同的是,interruptIdleWorkers方法在遍历线程池里的线程时,有一个w.tryLock()加锁判断,只有加锁成功的线程才会被调用interrupt方法。那什么情况下才能被加锁成功?什么情况下不能被加锁成功呢?这就需要我们继续回到线程执行的runWorker方法。

在上边runWorker方法代码的截图中,我刻意将w.lock()和w.unlock()调用用红框圈起。其实就是正运行在w.lock和w.unlock之间的线程将因为加锁失败,而不会被调用interrupt方法,换句话说,就是正在执行线程池里任务的线程不会被中断。

不管是被调用了interrupt的线程还是没被调用的线程,什么时候退出呢?这就要看getTask方法的返回是否为null了。

在getTask里的if判断(上文中getTask代码截图中上边红色方框的代码)中,由于线程池被shutdown方法修改为SHUTDOWN状态,SHUTDOWN大于等于SHUTDOWN成立没问题,但是SHUTDOWN不在大于等于STOP状态,所以只有队列为空,getTask方法才会返回null,导致线程退出。

总结:

当我们调用线程池的shuwdown方法时:

  • 如果线程正在执行线程池里的任务,即便任务处于阻塞状态,线程也不会被中断,而是继续执行。
  • 如果线程池阻塞等待从队列里读取任务,则会被唤醒,但是会继续判断队列是否为空,如果不为空会继续从队列里读取任务,为空则线程退出。

4.  关闭线程池

前面深入了解了两个线程池的关闭方法,相信安全关闭线程池已不再是个难题。

我们知道,使用shutdownNow方法,可能会引起报错,使用shutdown方法可能会导致线程关闭不了。所以,当我们使用shutdownNow方法关闭线程池时,一定要对任务里进行异常捕获。

当我们使用shuwdown方法关闭线程池时,一定要确保任务里不会有永久阻塞等待的逻辑,否则线程池就关闭不了。

最后,一定要记得,shutdownNow和shuwdown调用完,线程池并不是立马就关闭了,要想等待线程池关闭,还需调用awaitTermination方法来阻塞等待。

 

以上,是Java面试题【如何关闭线程池】的参考答案。

输出,是最好的学习方法

欢迎在评论区留下你的问题、笔记或知识点补充~

—end—

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧