iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >【Java】如何优雅的关闭线程池
  • 677
分享到

【Java】如何优雅的关闭线程池

java开发语言 2023-09-21 10:09:52 677人浏览 独家记忆
摘要

文章目录 背景一、线程中断 interrupt二、线程池的关闭 shutdown 方法2.1、第一步:advanceRunState(SHUTDOWN) 把线程池置为 SHUTDOWN2.2、

文章目录

背景

前几天在和同事聊一个需求,说是有个数据查询的功能,因为涉及到多个第三方接口调用,想用线程池并行来做。

很正常的一个方案,但是上线后发现,每次服务发布的时候,这个数据查询的功能就会挂掉,后来发现是线程池没有做好关闭,这里总结一下。

关键字:线程池、shutdown、shutdownNow、interrupt

一、线程中断 interrupt

先补一补基础的知识:线程中断。
线程中断的含义,并不是强制把运行中的线程给“咔嚓”中断,而是把线程的中断标志位置为true,这样等线程之后阻塞(wait、join、sleep)的时候,就会抛出 InterruptedException,程序通过捕获 InterruptedException 来做一定的善后处理,然后让线程退出。

来看个例子,下面这段代码是起一个线程,打印一百行文本,打印过程中,会把线程的中断标志位置为true

public static void test02() throws InterruptedException {    Thread t = new Thread(() -> {    for (int i = 0; i < 100; i++) {        System.out.println("process i=" + i + ",interrupted:" + Thread.currentThread().isInterrupted());    }    });    t.start();    Thread.sleep(1);    t.interrupt();}

看看控制台的输出,发现在打印到 57 的时候,中断标志位已经成功置为true了,但是线程任然在打印,说明只是设置了中断标志位,而不是直接粗暴的把线程中断。

...process i=55,interrupted:falseprocess i=56,interrupted:falseprocess i=57,interrupted:trueprocess i=58,interrupted:trueprocess i=59,interrupted:true...

再看看这个示例,同样是打印一百行文本,打印过程中会判断中断标志位,如果中断就自行退出。

public static void test02() throws InterruptedException {    Thread t = new Thread(() -> {    for (int i = 0; i < 100; i++) {        if (Thread.interrupted()) {            System.out.println("线程已中断,退出执行");            break;        }        System.out.println("process i=" + i + ",interrupted:" + Thread.currentThread().isInterrupted());    }    });    t.start();    Thread.sleep(1);    t.interrupt();}

控制台输出如下,:

process i=49,interrupted:falseprocess i=50,interrupted:falseprocess i=51,interrupted:false

线程已中断,退出执行

二、线程池的关闭 shutdown 方法

了解完线程中断,再来看看线程池的关闭方法。

关闭线程池有两个方法 shutdown() 和 shutdownNow(),具体有什么区别?我们先来看看 shutdown() 方法

     public void shutdown() {        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            checkShutdownAccess();            advanceRunState(SHUTDOWN); // 1. 把线程池的状态设置为 SHUTDOWN            interruptIdleWorkers(); // 2. 把空闲的工作线程置为中断            onShutdown(); // 3. 一个空实现,暂不用关注        } finally {            mainLock.unlock();        }        tryTerminate();    }

源码先看注释,翻译下:

启动有序关闭会执行以前提交的任务,但不接受任何新任务。
如果已经关闭,则调用不会产生额外的影响。
此方法不等待活动执行的任务终止。如果需要,可使用 awaitTermination() 做到这一点。

2.1、第一步:advanceRunState(SHUTDOWN) 把线程池置为 SHUTDOWN

线程池状态流转如下。调用 shutdown() 方法会把线程池的状态置为 SHUTDOWN,后续再往线程池提交任务就会被拒绝(execute() 方法中做了判断)。

在这里插入图片描述

2.2、第二步:interruptIdleWorkers() 把空闲的工作线程置为中断

interruptIdleWorkers() 方法遍历所有的工作线程,如果 tryLock() 成功,就把线程置为中断。
这里,如果 tryLock() 成功,说明对应的 woker 是一个空闲的,没有在执行任务的线程,如果没成功,说明对应的 worker 正在执行任务。也就是说,这里的中断,对正在执行中的任务并没有影响。

 private void interruptIdleWorkers(boolean onlyOne) {        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            for (Worker w : workers) {                Thread t = w.thread;                if (!t.isInterrupted() && w.tryLock()) {                    try {                        t.interrupt();                    } catch (SecurityException ignore) {                    } finally {                        w.unlock();                    }                }                if (onlyOne)                    break;            }        } finally {            mainLock.unlock();        }    }

2.3、 第三步:onShutdown() 一个空实现,暂不用关注

这个没啥,就是个留空的方法。
在这里插入图片描述

2.4、 小结

shutdown() 方法干两件事:

  • 把线程池状态置为 SHUTDOWN 状态
  • 中断空闲线程

我们来看个例子,加深下印象。

public static void test01() throws InterruptedException {        // corePoolSize 是 2,maximumPoolSize 是 2        ThreadPoolExecutor es = new ThreadPoolExecutor(2, 2,                60L, TimeUnit.SECONDS,                new LinkedBlockingQueue<>());        es.prestartAllCoreThreads(); // 启动所有 worker        es.execute(new Task()); // Task是一个访问某网站的 Http 请求,跑的慢,后面会贴出来完整代码,这里把他当做一个跑的慢的异步任务就行        es.shutdown();        es.execute(new Task()); // 在线程池 shutdown() 后 继续添加任务,这里预期是抛出异常    }

这个例子我们主要观察两个现象。

  • 一个是线程池会有两个woker( prestartAllCoreThreads() 方法的调用使得已启动就有两个 worker),其中一个正在执行,一个处于空闲。所以当调用shutdown() 方法,走进 interruptIdleWorkers() 的时候,只有那个空闲的线程会调用 t.interrupt()。
    在这里插入图片描述

  • 第二个是调用 shutdown() 方法后,再调用 execute() 时,会抛出异常,因为线程池的状态已经置为 SHUTDOWN,不再接受新的任务添加进来。

三、线程池的关闭 shutdownNow 方式

     public List<Runnable> shutdownNow() {        List<Runnable> tasks;        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            checkShutdownAccess();            advanceRunState(STOP); // 1:把线程池设置为STOP            interruptWorkers(); // 2.中断工作线程            tasks = drainQueue(); // 3.把线程池中的任务都 drain 出来        } finally {            mainLock.unlock();        }        tryTerminate();        return tasks;    }

注释的意思是:

尝试停止所有正在执行的任务,暂停正在等待的任务的处理,并返回等待执行的任务列表。从该方法返回时,这些任务将从任务队列中清空(移除)。
此方法不等待活动执行的任务终止。如果需要,可使用 awaitTermination() 做到这一点。
除了尽最大努力尝试停止处理主动执行的任务之外,没有其他保证。
此实现通过 Thread.Interrupt() 取消任务,因此任何无法响应中断的任务都可能永远不会终止。

3.1、第一步:advanceRunState() 把线程池设置为STOP

和 shutdown() 方法不同的是,shutdownNow() 方法会把线程池的状态设置为 STOP。

3.2、 第二步:interruptWorkers() 中断工作线程

interruptWorkers() 如下,可以看到,和 shutdown() 方法不同的是,所有的工作线程都调用了 interrupt() 方法

      private void interruptWorkers() {        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            for (Worker w : workers)                w.interruptIfStarted();        } finally {            mainLock.unlock();        }    }

3.3、第三步:drainQueue() 把线程池中的任务都 drain 出来

drainQueue() 方法如下,把阻塞队列里面等待的任务都拿出来,并返回。关闭线程池的时候,可以基于这个特性,把返回的任务都打印出来,做个记录。

      private List<Runnable> drainQueue() {        BlockingQueue<Runnable> q = workQueue;        ArrayList<Runnable> taskList = new ArrayList<Runnable>();        q.drainTo(taskList);        if (!q.isEmpty()) {            for (Runnable r : q.toArray(new Runnable[0])) {                if (q.remove(r))                    taskList.add(r);            }        }        return taskList;    }

3.4、小结

shutdownNow() 方法干三件事:

  • 把线程池状态置为 STOP 状态
  • 中断工作线程
  • 把线程池中的任务都 drain 出来并返回

我们来看个例子,代码合刚才的一样,只是关闭线程用的是shutdownNow()

public static void test01() throws InterruptedException {        // corePoolSize 是 1,maximumPoolSize 是 1,无限容量        ThreadPoolExecutor es = new ThreadPoolExecutor(1, 1,                60L, TimeUnit.SECONDS,                new LinkedBlockingQueue<>());        es.prestartAllCoreThreads(); // 启动所有 worker        es.execute(new Task()); // Task是一个访问某网站的 HTTP 请求,跑的慢,后面会贴出来完整代码,这里把他当做一个跑的慢的异步任务就行        es.execute(new Task());        List<Runnable> result = es.shutdownNow();        System.out.println(result);        es.execute(new Task()); // 在线程池 shutdownNow() 后 继续添加任务,这里预期是抛出异常    }

这个例子我们主要观察三个现象。
一个是线程池有两个woker,所以当调用shutdownNow() 方法,走进 interruptWorkers() 的时候,所有的 woker 都会调用 t.interrupt()。
在这里插入图片描述

第二个是 shutdownNow() 方法会返回还没来得及执行的task,并打印出来。
第三个是调用 shutdownNow() 方法后,再调用 execute() 时,会抛出异常,因为线程池的状态已经置为 STOP,不再接受新的任务添加
在这里插入图片描述

四、实战,与 JVM 钩子配合

实际工作中,我们一般是使用 shutdown() 方法,因为它比较“温和”,会等待我们把线程池中的任务都执行完,这里也已 shutdown() 方法为例。

我们回到最开头聊到的那个 case,机器重新发布,但是线程池中还有没执行完任务,机器一关,这些任务全部被kill,怎么办呢?有什么机制能够阻塞一下,等待这个任务执行完再关闭吗?

有的,用 JVM 的钩子!

实例代码如下,一个线程池,提交了三个任务去执行,执行完得半分钟。然后增加一个JVM的钩子,这个钩子可以简单理解为监听器,注册后,JVM在关闭的时候就会调用这个方法,调用完才会正式关闭JVM。

public static void test01() throws InterruptedException {        ThreadPoolExecutor es = new ThreadPoolExecutor(1, 1,                60L, TimeUnit.SECONDS,                new LinkedBlockingQueue<>());        es.execute(new Task());        es.execute(new Task());        es.execute(new Task());        Thread shutdownHook = new Thread(() -> {            es.shutdown();            try {                es.awaitTermination(3, TimeUnit.MINUTES);            } catch (InterruptedException e) {                e.printStackTrace();                System.out.println("等待超时,直接关闭");            }        });        Runtime.getRuntime().addShutdownHook(shutdownHook);    }

在机器上执行,会发现,我使用 ctrl + c (注意不是ctrl + z )关闭进程,会发现进程并没有直接关闭,线程池任然执行,一直等到线程池的任务执行完,进程才会正式退出。
在这里插入图片描述

怎么样,是不是很神奇。
本文中涉及的 Task 的源码如下。这个任务是对 stackoverflow 网站发起 10 次请求,用来模拟跑的比较慢的任务,当然这不是重点,可以忽略,有兴趣动手试一下本文代码的同学可以参考下。

 public static class Task implements Runnable {        @Override        public void run() {            System.out.println("task start");            for (int i = 0; i < 10; i++) {                httpGet();                System.out.println("task execute " + i);            }            System.out.println("task finish");        }        private void httpGet() {            String url = "https://stackoverflow.com/";            String result = "";            BufferedReader in = null;            try {                String urlName = url;                URL realUrl = new URL(urlName);                // 打开和URL之间的连接                URLConnection conn = realUrl.openConnection();                // 设置通用的请求属性                conn.setRequestProperty("accept", "*/*");                conn.setRequestProperty("connection", "Keep-Alive");                conn.setRequestProperty("user-agent",                        "Mozilla/4.0 (compatible; MSIE 6.0; windows NT 5.1; SV1)");                // 建立实际的连接                conn.connect();                // 获取所有响应头字段                Map<String, List<String>> map = conn.getHeaderFields();//                 遍历所有的响应头字段//                for (String key : map.keySet()) {//                    System.out.println(key + "--->" + map.get(key));//                }                // 定义BufferedReader输入流来读取URL的响应                in = new BufferedReader(                        new InputStreamReader(conn.getInputStream()));                String line;                while ((line = in.readLine()) != null) {                    result += "/n" + line;                }            } catch (Exception e) {                e.printStackTrace();            }            // 使用finally块来关闭输入流            finally {                try {                    if (in != null) {                        in.close();                    }                } catch (Exception ex) {                    ex.printStackTrace();                }            }//            System.out.print(result);        }    }

五、总结

想要优雅的关闭线程池,首先要理解线程中断的含义。
其次,关闭线程池有两种方式:shutdown() 和 shutdownNow(),二者最大的区别是 shutdown() 只是把空闲的 woker 置为中断,不影响正在运行的woker,并且会继续把待执行的任务给处理完。shutdonwNow() 则是把所有的 woker 都置为中断,待执行的任务全部抽出并返回,日常工作中更多是使用 shutdown()。
最后,单纯的使用 shutdown() 也不靠谱,还得使用 awaitTermination() 和 JVM 的钩子,才算优雅的关闭线程池。

来源地址:https://blog.csdn.net/u011397981/article/details/131325310

--结束END--

本文标题: 【Java】如何优雅的关闭线程池

本文链接: https://www.lsjlt.com/news/413812.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 【Java】如何优雅的关闭线程池
    文章目录 背景一、线程中断 interrupt二、线程池的关闭 shutdown 方法2.1、第一步:advanceRunState(SHUTDOWN) 把线程池置为 SHUTDOWN2.2、...
    99+
    2023-09-21
    java 开发语言
  • python多线程编程:如何优雅地关闭线程
    在并发编程中,我们可能会创建新线程,并在其中运行任务,可能由于一些原因,决定停止该线程。例如: 不再需要线程任务的结果了。应用程序正在关闭。线程执行可能已经出现了异常 关于python多线程编程知...
    99+
    2023-09-04
    python 开发语言
  • 详解Java如何关闭线程以及线程池
    目录前言1. 关闭线程1.1 volatile关键字1.2 intrrrupt()方法2.关闭线程池2.1 shutdownNow()方法2.2 shutdown()方法前言 这个问...
    99+
    2024-04-02
  • Java中该如何优雅的使用线程池详解
    目录为什么要用线程池?线程池线程池基本概念线程池接口定义和实现类ExecutorServiceScheduledExecutorService线程池工具类newFixedThread...
    99+
    2024-04-02
  • 详解Java线程池如何实现优雅退出
    目录shutdown()方法shutdownNow()方法awaitTermination(long, TimeUnit)方法在【高并发专题】中,我们从源码角度深度分析了线程池中那些...
    99+
    2024-04-02
  • Go如何优雅的关闭goroutine协程
    目录1.简介2.为什么需要关闭goroutine2.1 协程的生命周期2.2 协程的终止条件2.3 为什么需要主动关闭goroutine3.如何优雅得关闭goroutine3.1 传...
    99+
    2023-05-20
    Go关闭goroutine协程 Go关闭goroutine Go关闭协程
  • 怎么在java中关闭线程池
    本篇文章为大家展示了怎么在java中关闭线程池,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3. 客户端开发;4...
    99+
    2023-06-14
  • Java 线程池的艺术:优雅地管理并发
    线程池基础 线程池是一组预先创建并管理的线程,用于执行任务。它提供了以下主要好处: 资源优化:通过重用现有的线程,线程池消除了重复创建和销毁线程的开销,从而显着提高性能。 并发控制:通过限制同时执行的任务数量,线程池可以防止系统过载,确...
    99+
    2024-03-13
    线程池
  • 浅谈Java关闭线程池shutdown和shutdownNow的区别
    目录前言项目环境1.线程池示例2.shutdown3.isShutdown4.isTerminated5.awaitTermination6.shutdownNow7.shutdow...
    99+
    2024-04-02
  • go如何优雅关闭Graceful Shutdown服务
    目录Shutdown方法RegisterOnShutdown方法sync.WaitGroup处理退出函数Shutdown方法 Go1.8之后有了Shutdown方法,用来优雅的关闭(...
    99+
    2023-05-20
    go关闭服务 Graceful Shutdowng
  • C#如何优雅的结束线程
    大家都知道在C#里面,我们可以使用Thread.Start方法来启动一个线程,当我们想停止执行的线程时可以使用Thread.Abort方法来强制停止正在执行的线程,但是请注意,你确定...
    99+
    2024-04-02
  • java中线程池最实用的创建与关闭指南
    目录前言线程池创建 只需要执行shutdown就可以优雅关闭 执行shutdownNow关闭的测试 总结前言 在日常的开发工作当中,线程池往往承载着一个应用中最重要的业务逻辑,因此我...
    99+
    2024-04-02
  • Java线程池如何创建
    本文小编为大家详细介绍“Java线程池如何创建”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java线程池如何创建”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。线程池的好处可以实现线程的复用,避免重新创建线程和...
    99+
    2023-06-29
  • 如何查看java线程池的线程数量
    要查看Java线程池的线程数量,可以使用`getPoolSize()`方法来获取线程池中当前的线程数量。以下是一个示例代码:```j...
    99+
    2023-08-24
    java
  • Java ThreadPoolExecutor线程池有关介绍
    目录为什么要有线程池线程池状态ThreadPoolExecutor核心参数corePoolSizemaximumPoolSizekeepAliveTimeunitworkQueuet...
    99+
    2024-04-02
  • go语言开发中如何优雅得关闭协程方法
    目录1.简介2.为什么需要关闭goroutine2.1 协程的生命周期2.2 协程的终止条件2.3 为什么需要主动关闭goroutine3.如何优雅得关闭goroutine3.1 传...
    99+
    2023-05-20
    go得到关闭协程 go关闭协程
  • 如何在 Golang 中优雅地关闭 HTTP 服务器
    Golang (又称为 Go) 是一门相对比较年轻的编程语言,它被广泛应用于后端服务和 API 的开发中。而 HTTP 是一个常用的协议,很多 Golang 开发人员会选择使用 Golang 中自带的 HTTP 包来进行 HTTP 服务器的...
    99+
    2023-05-14
    Golang go语言 http
  • Java线程池的优点及池化技术的应用
    目录1.池化技术2.池化技术应用2.1 线程池2.2 内存池2.3 数据库连接池2.4 HttpClient连接池3.线程池介绍4.线程池优点分析优点1:复用线程,降低资源消耗优点2...
    99+
    2024-04-02
  • java中线程池的优缺点对比
    这篇文章将为大家详细讲解有关java中线程池的优缺点对比,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Java的优点是什么1. 简单,只需理解基本的概念,就可以编写适合于各种情况的应用程序;...
    99+
    2023-06-14
  • 解析golang中如何优雅地关闭http服务
    Golang是一种非常流行的编程语言,它具有高效的并发处理能力和优秀的性能表现。相信许多golang的开发人员都会遇到一个这样的问题,在golang中如何优雅地关闭http服务?首先,我们需要知道,创建一个http服务是比较容易的,只需要几...
    99+
    2023-05-14
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作