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() + "->执行");
            });
        }
    }
}

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

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

1.1、shutdown

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

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

1.2、isShutdown

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

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

1.3、isTerminated

第三种方法叫做isTerminated(),这个方法可以检测线程池是否真正“终结了”,这不仅代表线程池已关闭,同时代表线程池中的所有任务都已经执行完毕了。比如我们上面提到的情况,如果此时已经调用了shutdown方法,但是还有任务没有执行完,那么此时调用isShutdown方法返回的是true,而isTerminated方法则会返回false。直到在所有的任务都执行完了,调用isTerminated()方法才会返回true,这表示线程池已关闭并且线程池内部是空的,所有剩余的任务都执行完毕了。

1.4、awaitTermination

第四个方法叫做awaitTermination(),它本身并不是用来关闭线程池的,屙屎主要来判断线程池状态的。猜测的应用场景,开启一批线程,使用awaitTermination判断是否在指定时间内全部完成,完成的任务结束,没完成则线程超时,可以进行后续的工作,比如使用shutdownNow终止剩余的线程。

当我们给awaitTermination()方法传入的参数时10秒,那么它就会陷入10秒钟的等待,直到发生以下三种情况:

  • 等待期间(包括进入等待状态之前)线程池已关闭并且所有已提交的任务(包括正在执行的和队列中等待的)都执行完毕,相当于线程池已经“终结”了,方法便会返回true;
  • 等待超时时间后,第一种线程池“终结”的情况始终未发生,方法返回false;
  • 等待期间线程被中断,方法会抛出InterruptedException异常;

调用awaitTermination方法后当前线程会尝试等待一段指定的时间,如果在等待下时间内,线程池已关闭并且内部的任务都执行完了,也就是说线程池真正“终结”了,那么方法就返回 true,否则超时返回 fasle。

1.5、shutdownNow

最后一个方法时shutdownNow(),它和shutdown()的区别就是多了一个Now,表示立刻关闭的意思。不推荐使用这一种方式关闭线程池。在执行shutdownNow方法后,首先会给所有线程发送interrupt中断信号,尝试中断这些任务的执行,然后会将队列中正在等待的所有任务转移到一个List中并返回,我们可以根据返回的任务List来进行一些补救的操作,例如记录在案并在后期重试。

源码:

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

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

2、shutdown 和 shutdownNow 的区别

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