优雅的关闭Java线程池,这样做才是yyds( 三 )

4.4 总结shutdownNow() 方法干三件事:

  1. 把线程池状态置为 STOP 状态
  2. 中断工作线程
  3. 把线程池中的任务都 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(); // 启动所有 workeres.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() 。
优雅的关闭Java线程池,这样做才是yyds

文章插图
第二个是 shutdownNow() 方法会返回还没来得及执行的task , 并打印出来 。
第三个是调用 shutdownNow() 方法后 , 再调用 execute() 时,会抛出异常,因为线程池的状态已经置为 STOP,不再接受新的任务添加
优雅的关闭Java线程池,这样做才是yyds

文章插图
5 实战,与 JVM 钩子配合实际工作中,我们一般是使用 shutdown() 方法,因为它比较“温和”,会等待我们把线程池中的任务都执行完,这里也已 shutdown() 方法为例 。
我们回到最开头聊到的那个 case , 机器重新发布,但是线程池中还有没执行完任务,机器一关 , 这些任务全部被kill,怎么办呢?有什么机制能够阻塞一下,等待这个任务执行完再关闭吗?
有的 , 用 JVM 的钩子?。ㄉ钊肓私?JVM 钩子可以再看看这篇博文:扫盲 JVM 安全退出机制:shutdownHook,signalHandler[1])
实例代码如下,一个线程池 , 提交了三个任务去执行,执行完得半分钟 。然后增加一个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 )关闭进程,会发现进程并没有直接关闭,线程池任然执行,一直等到线程池的任务执行完,进程才会正式退出 。
优雅的关闭Java线程池,这样做才是yyds

文章插图
怎么样,是不是很神奇 。
本文中涉及的 Task 的源码如下 。这个任务是对 stackoverflow 网站发起 10 次请求 , 用来模拟跑的比较慢的任务,当然这不是重点,可以忽略,有兴趣动手试一下本文代码的同学可以参考下 。
public static class Task implements Runnable {@Overridepublic 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);}}


推荐阅读