ExecutorService的正确关闭方式

ExecutorService简介

ExecutorService提供了一个异步任务管理的容器,可以关闭并可以通过Future来跟踪异步任务状态

ExecutorService定义了两个容器关闭的方法

   /**
     * Initiates an orderly shutdown in which previously submitted
     * tasks are executed, but no new tasks will be accepted.
     * Invocation has no additional effect if already shut down.
     * 
     *  已经加入线程池的任务会被执行,但是不会再接收新的任务
     *
     */
    void shutdown();


    /**
     * Attempts to stop all actively executing tasks, halts the
     * processing of waiting tasks, and returns a list of the tasks
     * that were awaiting execution.
     * 
     * 尝试中断(执行中)或者停止(待执行)所有的任务
     */
    List<Runnable> shutdownNow();

 

线程池的背景知识

 

工作线程:工作线程从任务队列不停的取任务并执行,空闲线程会阻塞在任务队列上。

任务队列:任务提交,如果核心线程数不够,则创建worker并把此任务作为firstTask交付给worker(并不提交到任务队列,减少线程切换等性能损耗)。如果核心线程数满了,则把任务递交到任务队列,唤醒阻塞在任务队列上的空闲线程来拉取任务,同时看情况是否新增非核心工作线程

   线程池的五种状态与核心容器关系

 

线程池状态任务队列工作线程
RUNNING可以提交任务可以正常拉取任务并执行
SHUTDOWN不能再提交任务正常拉取任务并执行
STOP不能再提交任务,并且任务队列被清空

空闲线程被唤醒退出

执行中的线程被中断(如果没有阻塞,interrupt不生效)

TIDYING任务队列为空执行中的task全部完成或者中止
TERMIANTED任务队列为空terminate hook被执行

 

线程池shutdown方法

    public void shutdown() {
        //主锁,用于保护线程池的状态,将锁引用copy到本地线程栈
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //状态置为SHUTDOWN,此时在任务队列中的task与被工作线程持有的task任然会被正常执行
            advanceRunState(SHUTDOWN);
            //中断所有空闲的线程,工作线程状态机参见附录
            interruptIdleWorkers();
            //hook
            onShutdown();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

 

一个典型spring线程池的停止的示例,意图在bean被销毁后,等待线程池中的任务被执行完毕,再退出这个bean的生命周期。

 

我的期望是,在spring优雅的停机时,能够等待线程池中所有的任务被执行完成,再停止服务。很明显,这种做法是有问题的,shutdown只是给线程池一个信号,不再接收任务的提,但并不保证任务都会被正确的执行!!!

 

一种可行的方式

  void shutdownAndAwaitTermination(ExecutorService pool) {
    pool.shutdown();  
    try {
       if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
          
      }
    } catch (InterruptedException ie) {
      pool.shutdownNow();
     
      Thread.currentThread().interrupt();
    }
 }} 

事实上,我们先给线程池一个信号,让它不再接受任务提交请求,再者,注册一个hook,在线程池任务为空,并且工作线程全部关闭后通知我们(awaitTermination)。

 

awaitTermination方法分析:

    public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                //如果线程池是TERMINATED状态,则直接退出,否则阻塞等待唤醒
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

dubbo的线程池停止方法,具体方法在

com.alibaba.dubbo.common.utils.ExecutorUtil#gracefulShutdown
    public static void gracefulShutdown(Executor executor, int timeout) {
        if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
            return;
        }
        final ExecutorService es = (ExecutorService) executor;
        try {
            // Disable new tasks from being submitted
            es.shutdown();
        } catch (SecurityException ex2) {
            return;
        } catch (NullPointerException ex2) {
            return;
        }
        try {
            // Wait a while for existing tasks to terminate
            if (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
                es.shutdownNow();
            }
        } catch (InterruptedException ex) {
            es.shutdownNow();
            Thread.currentThread().interrupt();
        }
        //在超时时间内如果还是没有能够停止,新开一个线程池来执行没有来得及执行的任务
        if (!isTerminated(es)) {
            newThreadToCloseExecutor(es);
        }
    }

 

 

总结:  在读dubbo的源码时,发现dubbo线程停止的方法比我预想的要繁杂,于是研究dubbo为什么是这样关闭线程,好奇心驱使我重新读了一遍线程池源码,才一窥其中的原理。

shutdownNow和shuwdown调用完,线程池并不是立马就关闭了,要想等待线程池关闭,还需调用awaitTermination方法来判断。

同时,我们在设计一个容器时,不仅仅需要能够完成这个容器的功能,同时也需要给外部提供容器的生命周期窥探已经管理的功能,这样的设计才是有生命力的。

 

 

附:worker源码

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        .....
        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

woker线程为什么要继承AbstractQueuedSynchronizer??

在容器shutdown时,我们发现容器中的任务队列很好控制,只要加主锁,更新容器状态,就能实现任务不能提交。

但是对于woker,如何感知工作线程的状态已经启动或者关闭,这里实现了一个简单的AQS,事实上是为了实现工作线程的锁(用于对worker状态的同步控制)以及巧妙的更新了工作线程状态

worker状态:

-1:初始态,worker被创建,但还未开始获取任务

0:工作线程启动,但是暂时没有任务处理

1:worker获取到了一个任务,并在执行

 

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java中,`ExecutorService`是一个用于管理线程执行的工具,它通常在执行大量并行任务时使用。当你不再需要这个服务时,应该正确关闭它以释放系统资源。终止`ExecutorService`有以下几种方法: 1. **调用shutdown()方法**: - `ExecutorService shutdown()`:这会取消所有未完成的任务,但不会立即终止线程。新的任务会被丢弃(默认行为)。 ```java executorService.shutdown(); ``` 2. **调用shutdownNow()方法**: - `ExecutorService shutdownNow()`:强制停止所有正在执行的任务,并尝试停止接受新的任务。如果任务还没有开始执行,它们将被取消;如果任务正在执行,它们会被中断。 ```java List<Runnable> abandonedTasks = executorService.shutdownNow(); // 返回可能未完成的任务列表 ``` 3. **调用awaitTermination()方法**: - `boolean awaitTermination(long timeout, TimeUnit unit)`:等待所有任务完成后关闭服务,如果超时,则返回false。 ```java if (!executorService.awaitTermination(1, TimeUnit.MINUTES)) { System.err.println("Pool did not terminate"); } ``` 4. **在finally块中关闭**: 如果你不想等待任务完成,可以在try-finally代码块中关闭服务,确保资源被释放。 ```java try { executorService.submit(task); } finally { executorService.shutdown(); } ``` 记得,如果你使用的是`ThreadPoolExecutor`或`ScheduledThreadPoolExecutor`,还需要考虑线程池中的线程是否已经空闲,如果是,那么`shutdown()`或`shutdownNow()`可能就足够了。如果不确定,你可以检查`isTerminated()`方法,如果返回`true`,表示服务已经终止。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值