参考答案
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—