经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
面试官:说一说如何优雅的关闭线程池,我:shutdownNow,面试官:粗鲁!
来源:cnblogs  作者:JavaBuild  时间:2024/6/3 9:29:20  对本文有异议

写在开头


面试官:“小伙子,线程池使用过吗,来聊一聊它吧!”

我:“好的,然后巴拉巴拉一顿输出之前看过的build哥线程池十八问...”

面试官满意的点了点头,紧接着问道:“那你知道如何优雅的关闭线程池吗?”

我:“知道知道,直接调用shutdownNow()方法就好了呀!”

面试官脸色一变,微怒道:“粗鲁!你给我滚出去!!!”


优雅的关闭线程池

哈哈,上面的场景是build哥臆想出来的面试画面,我们现在步入正题,来看一看在线程池使用完成后如何优雅的关闭线程池。

在JDK 1.8 中,Java 并发工具包中 java.util.concurrent.ExecutorService 提供了 shutdown()、shutdownNow()这两种接口方法去关闭线程池,我们分别看一下。

shutdown()

  1. public void shutdown() {
  2. final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主锁
  3. mainLock.lock(); // 加锁以确保独占访问
  4. try {
  5. checkShutdownAccess(); // 检查是否有关闭的权限
  6. advanceRunState(SHUTDOWN); // 将执行器的状态更新为SHUTDOWN
  7. interruptIdleWorkers(); // 中断所有闲置的工作线程
  8. onShutdown(); // ScheduledThreadPoolExecutor中的挂钩方法,可供子类重写以进行额外操作
  9. } finally {
  10. mainLock.unlock(); // 无论try块如何退出都要释放锁
  11. }
  12. tryTerminate(); // 如果条件允许,尝试终止执行器
  13. }

在shutdown的源码中,会启动一次顺序关闭,在这次关闭中,执行器不再接受新任务,但会继续处理队列中的已存在任务,当所有任务都完成后,线程池中的线程会逐渐退出。

我们写一个小的demo来使用shutdown():

  1. public class TestService{
  2. public static void main(String[] args) {
  3. //创建固定 3 个线程的线程池,测试使用,工作中推荐ThreadPoolExecutor
  4. ExecutorService threadPool = Executors.newFixedThreadPool(3);
  5. //向线程池提交 10 个任务
  6. for (int i = 1; i <= 10; i++) {
  7. final int index = i;
  8. threadPool.submit(() -> {
  9. System.out.println("正在执行任务 " + index);
  10. //休眠 3 秒,模拟任务执行
  11. try {
  12. Thread.sleep(3000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. });
  17. }
  18. //休眠 4 秒
  19. try {
  20. Thread.sleep(4000);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. //关闭线程池
  25. threadPool.shutdown();
  26. }
  27. }

在这段测试代码中,我们构造了一个包含固定3线程数的线程池,循环提交10个任务,每个任务休眠3秒,但主程序休眠4秒后,会掉用shutdown方法,理论上,在第二个时间循环中,线程池被停止,所以最多执行完6个任务,但从输出中,我们丝毫感受不好线程何时被停止了。
输出:

  1. 正在执行任务 1
  2. 正在执行任务 3
  3. 正在执行任务 2
  4. 正在执行任务 4
  5. 正在执行任务 5
  6. 正在执行任务 6
  7. 正在执行任务 7
  8. 正在执行任务 8
  9. 正在执行任务 9
  10. 正在执行任务 10

shutdownNow()

  1. /**
  2. * 尝试停止所有正在执行的任务,停止处理等待的任务,
  3. * 并返回等待处理的任务列表。
  4. *
  5. * @return 从未开始执行的任务列表
  6. */
  7. public List<Runnable> shutdownNow() {
  8. List<Runnable> tasks; // 用于存储未执行的任务的列表
  9. final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主锁
  10. mainLock.lock(); // 加锁以确保独占访问
  11. try {
  12. checkShutdownAccess(); // 检查是否有关闭的权限
  13. advanceRunState(STOP); // 将执行器的状态更新为STOP
  14. interruptWorkers(); // 中断所有工作线程
  15. tasks = drainQueue(); // 清空队列并将结果放入任务列表中
  16. } finally {
  17. mainLock.unlock(); // 无论try块如何退出都要释放锁
  18. }
  19. tryTerminate(); // 如果条件允许,尝试终止执行器
  20. return tasks; // 返回队列中未被执行的任务列表
  21. }

与shutdown不同的是shutdownNow会尝试终止所有的正在执行的任务,清空队列,停止失败会抛出异常,并且返回未被执行的任务列表。

由于shutdownNow会有返回值,所以我们将上面的测试案例稍作改动后输出结果为:

image

这种会在控制台抛出异常的方式,同样也不优雅,所以我们继续往下看!

shutdown()+awaitTermination(long timeout, TimeUnit unit)

awaitTermination(long timeout, TimeUnit unit)是可以允许我们在调用shutdown方法后,再设置一个等待时间,如设置为5秒,则表示shutdown后5秒内线程池彻底终止,返回true,否则返回false;

这种方式里,我们将shutdown()结合awaitTermination(long timeout, TimeUnit unit)方法去使用,注意在调用 awaitTermination() 方法时,应该设置合理的超时时间,以避免程序长时间阻塞而导致性能问题,而且由于这个方法在超时后也会抛出异常,因此,我们在使用的时候要捕获并处理异常!

  1. public class TestService{
  2. public static void main(String[] args) {
  3. //创建固定 3 个线程的线程池
  4. ExecutorService threadPool = Executors.newFixedThreadPool(3);
  5. //向线程池提交 10 个任务
  6. for (int i = 1; i <= 10; i++) {
  7. final int index = i;
  8. threadPool.submit(() -> {
  9. System.out.println("正在执行任务 " + index);
  10. //休眠 3 秒
  11. try {
  12. Thread.sleep(3000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. });
  17. }
  18. //关闭线程池,设置等待超时时间 3 秒
  19. System.out.println("设置线程池关闭,等待 3 秒...");
  20. threadPool.shutdown();
  21. try {
  22. boolean isTermination = threadPool.awaitTermination(3, TimeUnit.SECONDS);
  23. System.out.println(isTermination ? "线程池已停止" : "线程池未停止");
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. //再等待超时时间 20 秒
  28. System.out.println("再等待 20 秒...");
  29. try {
  30. boolean isTermination = threadPool.awaitTermination(20, TimeUnit.SECONDS);
  31. System.out.println(isTermination ? "线程池已停止" : "线程池仍未停止,请检查!");
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }

输出:

  1. 设置线程池关闭,等待 3 秒...
  2. 正在执行任务 1
  3. 正在执行任务 2
  4. 正在执行任务 3
  5. 正在执行任务 4
  6. 正在执行任务 5
  7. 线程池未停止
  8. 再等待 20 秒...
  9. 正在执行任务 6
  10. 正在执行任务 7
  11. 正在执行任务 8
  12. 正在执行任务 9
  13. 正在执行任务 10
  14. 线程池已停止

从输出中我们可以看到,通过将两种方法结合使用,我们监控了整个线程池关闭的全流程,实现了优雅的关闭!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!
image

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!
image

原文链接:https://www.cnblogs.com/JavaBuild/p/18226822

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号