如何关闭线程池

为什么关闭线程池

线程池关闭的意义不仅仅在于结束线程执行,避免内存溢出,因为大多使用的场景并非上述示例那样朝生夕死。线程池一般是持续工作的全局场景,如数据库连接池。

  线程池大家都知道,用来实现资源复用的。避免了线程频繁的创建和销毁的开销。待到使用完毕后都要关闭线程池,以释放资源,避免线程全部泄漏白白的浪费了内存。容易导致内存吃紧的时候造成死机。

线程池有运行任务自然也有关闭任务,线程池手动关闭无非是两个方法,其实无非就是两个方法 shutdown()/shutdownNow()。自动关闭则需要注意线程池配置技巧。

线程池手动关闭

关闭方法

线程池有两个方法 shutdown()/shutdownNow()用来关闭,二者有着重要的区别:

  • shutdown() 执行后停止接受新任务,会把队列的任务执行完毕。

  • shutdownNow() 也是停止接受新任务,但会中断所有的任务,将线程池状态变为 stop。

下段源码是shutdown() 和shutdownNow(),从中可以看到两个方法的不同

    /**
     * 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(); advanceRunState(SHUTDOWN); interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } tryTerminate(); } /** * 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(); advanceRunState(STOP); interruptWorkers(); tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; }

  1. shutdown()将线程池的状态修改为SHUTDOWN状态,然后调用interruptIdleWorkers方法,来中断空闲的线程。

方法是安全的关闭线程池,调用shutdown方法后,不是立即关闭线程池,而是在线程池中执行很多任务,或者等待队列中执行任务,等待所有任务完成后关闭线程池

  1. shutdownNow() 方法的执行逻辑:将线程池状态修改为STOP,然后调用线程池里的所有线程的interrupt方法。

shutdownNow()立即关闭线程池,首先向线程池中的线程发送中断信号,尝试中断线程,然后将等待队列的任务返回调用人员,让调用人员补救这些任务。

通常可以根据自己的业务需求,选择合适的方法停止线程池。例如,通常可以用shutdown()的方法关闭,完成提交的任务,但如果情况紧急,可以用shutdownnow方法加快线程池的结束速度。

我通常是按照以下方式关闭线程池的:

/**
 * @author Evan Walker
 * @version 1.0
 * @desc 线程池关闭demo
 */
public class ThreadPoolExecutorDemo {
    private final static Logger logger = LoggerFactory.getLogger(ThreadPoolExecutorDemo.class);
    //起的线程数
    private static final Integer THREAD_NUM = 5;
    //创建线程池
    ThreadPoolExecutor pool = new ThreadPoolExecutor(THREAD_NUM, THREAD_NUM * 2, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));

    public void ThreadPoolExecutorClose(Runnable command){
        long start= System.currentTimeMillis();
        for (int i=0; i <= 5; i++) {
            pool.execute(command);
        }
        pool.shutdown();
        try {
            while (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
                logger.info("线程还在执行。。。");
            }
        }catch (InterruptedException e){
            logger.error("ThreadPoolExecutor InterruptedException ",e);
        }
        long end= System.currentTimeMillis();
        logger.info("一共处理了【{}】", (end - start));
    }
}

也可以设置一个shutdownHook来关闭线程池:

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    try {
        pool.shutdown();
        while(!pool.awaitTermination(10,TimeUnit.SECONDS)) {}
    } catch(Exception e) {
         logger.error("pool shutdown has exception ",e);
    }
}));

pool.awaitTermination(1, TimeUnit.SECONDS) 会每隔一秒钟检查一次是否执行完毕(状态为 TERMINATED),当从 while 循环退出时就表明线程池已经完全终止了。

关闭状态判断

1 isShutdown

boolean isShutdown(),此方法在线程已经开始关闭的时候返回true,其他时候返回false,即是否调用了线程池的shutdown或shutdownnow方法,返回true不代表线程池已经终止了,还有可能是需要线程池需要等待正在执行的任务或任务队列中的任务执行完。

2 isTerminated

此方法返回true表示线程池的所有任务都执行完毕了,线程池终结了;如果线程池调用了shutdown,但是任务还没执行完,isShutdown返回true,而isTerminated返回false。

3.awaittermination方法是判断线程池是否完全关闭,与isterminated相似,但接受等待时间。调用该方法可能发生以下情况

(1)等待期间(包括进入等待状态)线程池关闭,提交的任务(包括执行中和队列中等待的)全部完成,相当于线程池结束,方法返回true

(2)等待超时后,最初的线程池没有发生结束法回到false

(3)等待期间线程中断,方法会抛出互联网异常。

线程池自动关闭

ThreadPoolExecutor 类源码中有这样一句话:

  • A pool that is no longer referenced in a program and has no remaining threads will be shutdown automatically.

  • 没有引用指向且没有剩余线程的线程池将会自动关闭。

先来看一下 ThreadPoolExecutor 参数最全的构造方法:

/**
 * @param corePoolSize the number of threads to keep in the pool, even
 *           if they are idle, unless {@code allowCoreThreadTimeOut} is set
 *        核心线程数:即使是空闲状态也可以在线程池存活的线程数量,除非           
 *        allowCoreThreadTimeOut 设置为 true。
 * @param keepAliveTime when the number of threads is greater than
 *        the core, this is the maximum time that excess idle threads
 *        will wait for new tasks before terminating.
 *         存活时间:对于超出核心线程数的线程,空闲时间一旦达到存活时间,就会被销毁。
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                           RejectedExecutionHandler handler) { ... ... }

方案一:自动关闭线程池:核心线程数为 0 并指定线程存活时间

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 重点关注 corePoolSize 和 keepAliveTime,其他参数不重要
        ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 5,
                15L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
        for (int i = 0; i < 20; i++) {
            executor.execute(() -> {
               // 简单地打印当前线程名称
                System.out.println(Thread.currentThread().getName());
            });
        }
    }
}
如何关闭线程池_第1张图片

通过执行打印可以发现,线程池在15秒之后自动关闭,因为它核心线程核心线程数为 0 并指定线程存活时间,且后续没有继续使用到该线程池,在其最后一个线程执行完毕后15秒之后,会优雅的关闭线程池。

而如果将corePoolSize设置为大于0的数字,再运行以上代码,那么线程池将一直处于等待状态而不能关闭,因为核心线程不受keepAliveTime控制,所以会一直存活,程序也将一直不能结束。运行效果如下 (corePoolSize设置为5,其他参数不变)

如何关闭线程池_第2张图片

线程池工作时当有新的任务到来时,程序会先判断线程池当前线程数是否达到corePoolSize(核心线程数),没达到则创建线程执行任务,达到则尝试将任务放入任务队列 (workQueue)。如果将corePoolSize设置为0的话,新到来的任务会永远优先被放入任务队列,然后等待被处理,这显然会影响程序的执行效率。

所以就有了下面的解决方案。

方案二:自动关闭线程池:通过 allowCoreThreadTimeOut 控制核心线程存活时间

JDK 1.6 开始,ThreadPoolExecutor 类新增了一个allowCoreThreadTimeOut字段:

/**
 * If false (default), core threads stay alive even when idle.
 * If true, core threads use keepAliveTime to time out waiting
 * for work.
 * 默认为false,核心线程处于空闲状态也可一直存活
 * 如果设置为true,核心线程的存活状态将受keepAliveTime控制,超时将被销毁
 */
private volatile boolean allowCoreThreadTimeOut;

这个字段值默认为false,可使用allowCoreThreadTimeOut()方法对其进行设置,如果设置为 true,那么核心线程数也将受keepAliveTime控制,此方法源码如下:

public void allowCoreThreadTimeOut(boolean value) {
    // 核心线程存活时间必须大于0,一旦开启,keepAliveTime 也必须大于0
    if (value && keepAliveTime <= 0)
        throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
    // 将 allowCoreThreadTimeOut 值设为传入的参数值
    if (value != allowCoreThreadTimeOut) {
        allowCoreThreadTimeOut = value;
        // 开启后,清理所有的超时空闲线程,包括核心线程
        if (value)
            interruptIdleWorkers();
    }
}
public class ThreadPoolTest {
    public static void main(String[] args) {
        // 这里把corePoolSize设为5,keepAliveTime保持不变
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
                15L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
        // 允许核心线程超时销毁
        executor.allowCoreThreadTimeOut(true);
        for (int i = 0; i < 20; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName());
            });
        }
    }
}

运行结果

如何关闭线程池_第3张图片

可以看到,程序在打印结束后等待了15s,然后自行退出,说明线程池已自动关闭,也就是allowCoreThreadTimeOut()方法发挥了作用。这样,我们就实现了可自动关闭且核心线程数不为0的线程池。

方案三:自动关闭线程池:线程池中的线程设置为守护线程

public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5,
                15L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15),new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, r.hashCode()+"");
                thread.setDaemon(true);//设置成守护线程
                return thread;
            }
        });
        for (int i = 0; i < 20; i++) {
            executor.execute(() -> {
                // 简单地打印当前线程名称
                System.out.println(Thread.currentThread().getName());
            });
        }
    }
}

虽然corePoolSize>0,而且没有设置allowCoreThreadTimeOut,但是在创建线程池时通过ThreadFactory指定了线程为守护线程。当线程打印结束后,无需等待程序正常退出,说明线程池关闭了。

PS:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。

文章参考:

https://segmentfault.com/a/1190000021225019

https://blog.csdn.net/liuxiao723846/article/details/127922625

更多消息资讯,请访问昂焱数据。https://www.ayshuju.com/home

你可能感兴趣的:(java)