浅谈Java关闭线程池shutdown和shutdownNow的区别

目录
  • 前言
  • 项目环境
  • 1.线程池示例
  • 2.shutdown
  • 3.isShutdown
  • 4.isTerminated
  • 5.awaitTermination
  • 6.shutdownNow
  • 7.shutdown 和 shutdownNow 的区别?

前言

本章分为两个议题

  • 如何正确关闭线程池
  • shutdown 和 shutdownNow 的区别

项目环境

1.线程池示例

public class ShutDownThreadPoolDemo {

    private ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        new ShutDownThreadPoolDemo().executeTask();
    }

    public void executeTask() {
        for (int i = 0; i < 100; i++) {
            service.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "->执行");
            });
        }
    }
}

执行结果

pool-1-thread-2->执行
pool-1-thread-3->执行
pool-1-thread-1->执行
pool-1-thread-4->执行
pool-1-thread-5->执行
pool-1-thread-6->执行
...

执行完成之后,主线程会一直阻塞,那么如何关闭线程池呢?本章介绍 5 种在 ThreadPoolExecutor 中涉及关闭线程池的方法,如下所示

  • void shutdown
  • boolean isShutdown
  • boolean isTerminated
  • boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
  • List<Runnable> shutdownNow

2.shutdown

第一种方法叫作 shutdown(),它可以安全地关闭一个线程池,调用 shutdown() 方法之后线程池并不是立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务,调用 shutdown() 方法后线程池会在执行完正在执行的任务和队列中等待的任务后才彻底关闭。

调用 shutdown() 方法后如果还有新的任务被提交,线程池则会根据拒绝策略直接拒绝后续新提交的任务。

这段源码位置(jdk 1.8 版本)

java.util.concurrent.ThreadPoolExecutor#execute

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 线程池中的线程比核心线程数少
        if (workerCountOf(c) < corePoolSize) {
            // 新建一个核心线程执行任务
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 核心线程已满,但是任务队列未满,添加到队列中
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
            if (! isRunning(recheck) && remove(command))
                // 如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
                reject(command);
            else if (workerCountOf(recheck) == 0)
                // 如果之前的线程已经被销毁完,新建一个非核心线程
                addWorker(null, false);
        }
        // 核心线程池已满,队列已满,尝试创建一个非核心新的线程
        else if (!addWorker(command, false))
            // 如果创建新线程失败,说明线程池关闭或者线程池满了,拒绝任务
            reject(command);
    }

1373 行 if (! isRunning(recheck) && remove(command))
如果线程池被关闭,将当前的任务从任务队列中移除成功,并拒绝该任务

1378 行 else if (!addWorker(command, false))
如果创建新线程失败,说明线程池关闭或者线程池满了,拒绝任务。

3.isShutdown

第二个方法叫作 isShutdown(),它可以返回 true 或者 false 来判断线程池是否已经开始了关闭工作,也就是是否执行了 shutdown 或者 shutdownNow 方法。

这里需要注意,如果调用 isShutdown() 方法的返回的结果为 true 并不代表线程池此时已经彻底关闭了,这仅仅代表线程池开始了关闭的流程,也就是说,此时可能线程池中依然有线程在执行任务,队列里也可能有等待被执行的任务。

4.isTerminated

第三种方法叫作 isTerminated(),这个方法可以检测线程池是否真正“终结”了,这不仅代表线程池已关闭,同时代表线程池中的所有任务都已经都执行完毕了。

比如我们上面提到的情况,如果此时已经调用了 shutdown 方法,但是还有任务没有执行完,那么此时调用 isShutdown 方法返回的是 true,而 isTerminated 方法则会返回 false。

直到所有任务都执行完毕了,调用 isTerminated() 方法才会返回 true,这表示线程池已关闭并且线程池内部是空的,所有剩余的任务都执行完毕了。

5.awaitTermination

第四个方法叫作 awaitTermination(),它本身并不是用来关闭线程池的,而是主要用来判断线程池状态的。

比如我们给 awaitTermination 方法传入的参数是 10 秒,那么它就会陷入 10 秒钟的等待,直到发生以下三种情况之一:

  • 等待期间(包括进入等待状态之前)线程池已关闭并且所有已提交的任务(包括正在执行的和队列中等待的)都执行完毕,相当于线程池已经“终结”了,方法便会返回 true
  • 等待超时时间到后,第一种线程池“终结”的情况始终未发生,方法返回 false
  • 等待期间线程被中断,方法会抛出 InterruptedException 异常
  • 调用 awaitTermination 方法后当前线程会尝试等待一段指定的时间,如果在等待时间内,线程池已关闭并且内部的任务都执行完毕了,也就是说线程池真正“终结”了,那么方法就返回 true,否则超时返回 fasle。

6.shutdownNow

最后一个方法是 shutdownNow(),它和 shutdown() 的区别就是多了一个 Now,表示立刻关闭的意思,不推荐使用这一种方式关闭线程池。

在执行 shutdownNow 方法之后,首先会给所有线程池中的线程发送 interrupt 中断信号,尝试中断这些任务的执行,然后会将任务队列中正在等待的所有任务转移到一个 List 中并返回,我们可以根据返回的任务 List 来进行一些补救的操作,例如记录在案并在后期重试。

shutdownNow 源码如下:

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

interruptWorkers

  • 让每一个已经启动的线程都中断,这样线程就可以在执行任务期间检测到中断信号并进行相应的处理,提前结束任务

7.shutdown 和 shutdownNow 的区别?

  • shutdown 会等待线程池中的任务执行完成之后关闭线程池,而 shutdownNow 会给所有线程发送中断信号,中断任务执行,然后关闭线程池
  • shutdown 没有返回值,而 shutdownNow 会返回关闭前任务队列中未执行的任务集合(List)

到此这篇关于浅谈Java关闭线程池shutdown和shutdownNow的区别的文章就介绍到这了,更多相关Java关闭线程池shutdown和shutdownNow内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java虚拟机钩子关闭函数addShutdownHook的操作

    当jvm虚拟机被关闭的时候,可能我们需要做一些处理,比如对连接的关闭,或者对一些必要信息的存储等等操作,这里就可以借助于虚拟机提供的钩子函数,当jvm虚拟机关闭之前会去调用addShutdownHook注册的线程钩子. 这里做一个小实验,项目结构如下: 1.自定义的ApplicationContext的上下文 package cn.lijie; public class ApplicationContext { private static ApplicationContext applicat

  • java 优雅关闭线程池的方案

    我们经常在项目中使用的线程池,但是是否关心过线程池的关闭呢,可能很多时候直接再项目中直接创建线程池让它一直运行当任务执行结束不在需要了也不去关闭,这其实是存在非常大的风险的,大量的线程常驻在后台对系统资源的占用是巨大的 ,甚至引发异常.所以在我们平时使用线程池时需要注意优雅的关闭,这样可以保证资源的管控. 在 Java 中和关闭线程池相关的方法主要有如下: void shutdown() List<Runnable> shutDownNow boolean awaitTermination b

  • Java ShutdownHook原理详解

    ShutdownHook介绍 在java程序中,很容易在进程结束时添加一个钩子,即ShutdownHook.通常在程序启动时加入以下代码即可 Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run() { System.out.println("I'm shutdown hook..."); } }); 有了ShutdownHook我们可以 在进程结束时做一些善后工作,例如释放占用的资源,

  • Java Shutdown Hook场景使用及源码分析

    目录 背景 Shutdown Hook 介绍 关闭钩子被调用场景 注意事项 实践 Shutdown Hook 在 Spring 中的运用 背景 如果想在 Java 进程退出时,包括正常和异常退出,做一些额外处理工作,例如资源清理,对象销毁,内存数据持久化到磁盘,等待线程池处理完所有任务等等.特别是进程异常挂掉的情况,如果一些重要状态没及时保留下来,或线程池的任务没被处理完,有可能会造成严重问题.那该怎么办呢? Java 中的 Shutdown Hook 提供了比较好的方案.我们可以通过 Java

  • 浅谈Java关闭线程池shutdown和shutdownNow的区别

    目录 前言 项目环境 1.线程池示例 2.shutdown 3.isShutdown 4.isTerminated 5.awaitTermination 6.shutdownNow 7.shutdown 和 shutdownNow 的区别? 前言 本章分为两个议题 如何正确关闭线程池 shutdown 和 shutdownNow 的区别 项目环境 jdk 1.8 github 地址:https://github.com/huajiexiewenfeng/java-concurrent 本章模块:

  • 浅谈一下python线程池简单应用

    一.线程池简介 传统多线程方案会使用“即时创建,即时销毁”的策略.尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务时执行时间较短,而且执行次数及其频繁,那么服务器将处于不停的创建线程,销毁线程的状态. 一个线程的运行时间可以分为三部分:线程的启动时间.线程体的运行时间和线程的销毁时间. 在多线程处理的情景中,如果线程不能被重用,就意味着每次线程运行都要经过启动.销毁和运行3个过程.这必然会增加系统相应的时间,减低了效率. 线程池在系统启动时即创建大量空闲的线程,程序只要

  • 浅谈java+内存分配及变量存储位置的区别

    Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识.一般Java在内存分配时会涉及到以下区域: ◆寄存器:我们在程序中无法控制 ◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中(new 出来的对象) ◆堆:存放用new产生的数据 ◆静态域:存放在对象中用static定义的静态成员 ◆常量池:存放常量 ◆非RAM存储:硬盘等永久

  • 浅谈Java中的四种引用方式的区别

    强引用.软引用.弱引用.虚引用的概念 强引用(StrongReference) 强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用: Object object = new Object(); String str = "hello"; 只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象. 比如下面这段代码: public class Main { publi

  • 浅谈Java中IO和NIO的本质和区别

    IO的本质 IO的作用就是从外部系统读取数据到java程序中,或者把java程序中输出的数据写回到外部系统.这里的外部系统可能是磁盘,网络流等等. 因为对所有的外部数据的处理都是由操作系统内核来实现的,对于java应用程序来说,只是调用操作系统中相应的接口方法,从而和外部数据进行交互. 所有IO的本质就是对Buffer的处理,我们把数据放入Buffer供系统写入外部数据,或者从系统Buffer中读取从外部系统中读取的数据.如下图所示: 用户空间也就是我们自己的java程序有一个Buffer,系统

  • 浅谈java中BigDecimal的equals与compareTo的区别

    这两天在处理支付金额校验的时候出现了点问题,有个金额比较我用了BigDecimal的equals方法来比较两个金额是否相等,结果导致金额比较出现错误(比如3.0与3.00的比较等). [注:以下所讲都是以sun jdk 1.4.2版本为例,其他版本实现未必一致,请忽略] 首先看一下BigDecimal的equals方法: public boolean equals(Object x){ if (!(x instanceof BigDecimal)) return false; BigDecima

  • 浅谈java中类名.class, class.forName(), getClass()的区别

    Class对象的生成方式如下: 1.类名.class    说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作.返回Class的对象 2.Class.forName("类名字符串")  (注:类名字符串是包名+类名) 说明:装入类,并做类的静态初始化,返回Class的对象 3.实例对象.getClass()  说明:对类进行静态初始化.非静态初始化:返回引用o运行时真正所指的对象(因为:子对象的引用可能会赋给父对象的引用变量中)所属的类的Cla

  • 浅谈java中六大时间类的使用和区别

    java.util.Date java.sql.Date java.sql.Time java.sql.Timestamp java.text.SimpleDateFormat java.util.Calendar java.util.Date日期格式为:年月日时分秒 java.sql.Date日期格式为:年月日 java.sql.Time日期格式为:时分秒 java.sql.Timestamp日期格式为:年月日时分秒纳秒(毫微秒) 从数据库中取出来的日期一般都用getTimestamp()方法

  • 浅谈Android中线程池的管理

    说到线程就要说说线程机制 Handler,Looper,MessageQueue 可以说是三座大山了 Handler Handler 其实就是一个处理者,或者说一个发送者,它会把消息发送给消息队列,也就是Looper,然后在一个无限循环队列中进行取出消息的操作 mMyHandler.sendMessage(mMessage); 这句话就是我耗时操作处理完了,我发送过去了! 然后在接受的地方处理!简单理解是不是很简单. 一般我们在项目中异步操作都是怎么做的呢? // 这里开启一个子线程进行耗时操作

  • 浅谈Java中BIO、NIO和AIO的区别和应用场景

    最近一直在准备面试,为了使自己的Java水平更上一个档次,拜读了李林峰老师的<Netty权威指南>,了解了Java关于IO的发展和最新的技术,真是受益匪浅,现在把我总结的关于BIO.NIO和AIO的区别和应用场景概述一遍. 在此之前,先弄清几个概念: 1.同步:使用同步IO时,Java自己处理IO读写. 2.异步:使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS,完成后OS通知Java处理(回调). 3.阻塞:使用阻塞IO时,Java调用会一直阻塞到读写完成

随机推荐