线程池关闭不合理,导致应用无法正常stop的情况

在上一篇博客中,我使用了线程池进行管理线程,达到线程复用的效果。

详情参考:线程池+CountDownLatch优化代码,提高程序执行效率

 

程序启动、运行皆无异常,线程池确实对程序中创建的线程进行管理,但是,在我关闭tomcat时,无法正常关闭,程序出现报错。报错信息如下:

05-Apr-2020 19:09:45.003 璀﹀憡 [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads 
The web application [ROOT] appears to have started a thread named [pool-5-thread-2] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 sun.misc.Unsafe.park(Native Method)
 java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
 java.util.concurrent.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:492)
 java.util.concurrent.LinkedBlockingDeque.take(LinkedBlockingDeque.java:680)
 java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 java.lang.Thread.run(Thread.java:748

 注意看这行报错信息:

The web application [ROOT] appears to have started a thread named [pool-5-thread-2] but has failed to stop it. This is very likely to create a memory leak. 

异常信息显示: 应用程序启动了一个名为pool-5-thread-2的线程,但是关闭失败,这很可能导致内存泄漏。

很明显,tomcat没能关掉ThreadPoolExecutor的核心线程,因此需要在关闭tomcat前手动关闭。

如何手动关闭线程池请参考:停止Tomcat webapp 线程池报错的尝试解决

那如果我不想手动关闭线程池怎么办?

使用Spring中中的 ThreadPoolTaskExecutor。将线程池的关闭交给Spring去管理。

在applicationContext.xml中加入

 
    
        
        
        
        
        
        
        
        
        
        
            
        
    

程序中:

    @Autowired
    private ThreadPoolTaskExecutor threadPool;

     threadPool.execute(() -> {
            model.addAttribute("types", typeService.findHottestType(6));
            countDownLatch.countDown();
        });
     threadPool.execute(() -> {
            model.addAttribute("tags", tagService.findHottestTag(6));
            countDownLatch.countDown();
        });
     threadPool.execute(() -> {
            model.addAttribute("blogs", blogService.findHotestBlogs());
            countDownLatch.countDown();
        });
     threadPool.execute(() -> {
            model.addAttribute("newBlogs", blogService.findNewestBlogs(8));
            countDownLatch.countDown();
        });

再次启动程序,测试,关闭tomcat,报错信息不再提示。

 

如何优雅安全的关闭线程池?

线程池关闭不合理,导致应用⽆法正常stop的情况,这是我们很容易忽略的。

下面就以ThreadPoolExecutor为例,来介绍下如何优雅的关闭线程池。

线程中断

 

在介绍线程池关闭之前,先介绍下Thread的interrupt。

在程序中,我们是不能随便中断⼀个线程的,因为这是极其不安全的操作,我们⽆法知道这个线程正运⾏在什么状态,它可能持有某把锁,强⾏中断可能导致锁不能释放的问题;或者线程可能在操作数据库,强⾏中断导致数据不一致,从而混乱的问题。正因此,Java⾥将Thread的stop⽅法设置为过时,以禁⽌⼤家使⽤。

⼀个线程什么时候可以退出呢?当然只有线程自⼰才能知道。

所以我们这⾥要说的Thread的interrrupt⽅法,本质不是⽤来中断一个线程。而是将线程设置⼀个中断状态。当我们调⽤线程的interrupt方法,它有两个作⽤用:

1、如果此线程处于阻塞状态(比如调⽤了wait方法,io等待),则会立刻退出阻塞,并抛出InterruptedException异常,线程就可以通过捕获InterruptedException来做⼀定的处理,然后让线程退出。

2、如果此线程正处于运行之中,则线程不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以线程要在适当的位置通过调用isInterrupted方法来查看自⼰是否被中断,并做退出操作。

注:如果线程的interrupt方法先被调用,然后线程调用阻塞方法进入阻塞状态,InterruptedException异常依旧会抛出。如果线程捕获InterruptedException异常后,继续调用阻塞方法, 将不再触发InterruptedException异常。

线程池的关闭

 

线程池提供了两个关闭方法,shutdownNow和shuwdown⽅法。

shutdownNow⽅法的解释是:线程池拒接收新提交的任务,同时⽴刻关闭线程池,线程池里的任务不再执行

shutdown⽅法的解释是:线程池拒接收新提交的任务,同时等待线程池⾥的任务执行完毕后关闭线程池。

以上的说法虽然没错,但是还有很多的细节问题,比如调用shutdown⽅法后,正在执⾏的任务的线程会做出什么反应?正在等待任务的线程又会做出什么反应?线程在什么情况下才会彻底退出。如果不了解这些细节,在关闭线程池时就难免遇到,像线程池关闭不了,关闭线程池出现报错等情况。

再说这些关闭线程池细节问题之前,需要强调一点的是,调用完shutdownNow和shuwdown⽅法后,并不代表线程池已经完成关闭操作,它只是异步的通知线程池进行关闭处理。如果要同步等待线程池彻底关闭后才继续往下执行,需要调⽤awaitTermination⽅法进⾏同步等待。

 

我们来看看shutdownNow和shuwdown⽅法的源码:

shutdown:

/**
     * 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.
     *
     * 

This method does not wait for previously submitted tasks to * complete execution. Use {@link #awaitTermination awaitTermination} * to do that. * * @throws SecurityException {@inheritDoc} */ public void shutdown() { final ReentrantLock mainLock = this.mainLock; // 上锁,确保同一时间只能有一个线程执行此操作 mainLock.lock(); try { // 检查方法调用方是否用权限关闭线程池以及中断工作线程 checkShutdownAccess(); // 将线程池运行状态设置为SHUTDOWN advanceRunState(SHUTDOWN); // 中断所有空闲线程 interruptIdleWorkers(); // 此方法在ThreadPoolExecutor中是空实现,具体实现在其子类ScheduledThreadPoolExecutor // 中,用于取消延时任务。 onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } // 尝试将线程池置为TERMINATED状态 tryTerminate(); }

 

 

 

 

 

 

shutdownNow:

    /**
     * Attempts to stop all actively executing tasks, halts the
     * processing of waiting tasks, and returns a list of the tasks
     * that were awaiting execution. These tasks are drained (removed)
     * from the task queue upon return from this method.
     *
     * 

This method does not wait for actively executing tasks to * terminate. Use {@link #awaitTermination awaitTermination} to * do that. * *

There are no guarantees beyond best-effort attempts to stop * processing actively executing tasks. This implementation * cancels tasks via {@link Thread#interrupt}, so any task that * fails to respond to interrupts may never terminate. * * @throws SecurityException {@inheritDoc} */ public List shutdownNow() { List tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 检查方法调用方是否用权限关闭线程池以及中断工作线程 checkShutdownAccess(); // 将线程池运行状态置为STOP advanceRunState(STOP); // 中断所有线程,包括正在运行的线程 interruptWorkers(); // 将未执行的任务移入列表中 tasks = drainQueue(); } finally { mainLock.unlock(); } // 尝试将线程池置为TERMINATED状态 tryTerminate(); return tasks; }

具体源码解释请参考:关闭线程池的正确姿势

 

 

 

 

 

 

 

你可能感兴趣的:(线程池,多线程,线程池)