如何关闭优雅关闭线程

前言

最近,遇到一个情况,引用的一些外部的cloud的包在调用的时候的时候报错了,看报错的内容是

The current thread was interrupted at xxxxx

后面就是堆栈信息, 在代码中是try catch(Exception e)
这段代码的,但是这个线程最后是阻断了,所以无法继续运行下去
于是...

我们来总结一下,关闭线程的方法,和如何优雅的关闭线程

1.interrupt
首先我们来看看我们遇到的线程中断

public static void test() throws InterruptedException {

    Thread t = new Thread(() -> {
    for (int i = 0; i < 100; i++) {
        System.out.println("process i=" + i + ",interrupted:" + Thread.currentThread().isInterrupted());
    }
    });
    t.start();
    Thread.sleep(1);
    t.interrupt();
}

以上的代码执行结果:

...

process i=55,interrupted:false

process i=56,interrupted:false

process i=57,interrupted:true

process i=58,interrupted:true

process i=59,interrupted:true

...

在57的时候,interrupt的标识变成true了,但是线程依旧在打印,
但是如果我们改成:

public static void test() throws InterruptedException {

    Thread t = new Thread(() -> {
    for (int i = 0; i < 100; i++) {
        if (Thread.interrupted()) {
            System.out.println("线程已中断,退出执行");
            break;
        }
        System.out.println("process i=" + i + ",interrupted:" + Thread.currentThread().isInterrupted());
    }
    });
    t.start();
    Thread.sleep(1);
    t.interrupt();
}

则会得到以下的结果:

process i=49,interrupted:false

process i=50,interrupted:false

process i=51,interrupted:false

线程已中断,退出执行

由此我们可以得到结论,Thread的interrupt()是不会直接关闭线程的,相当于标记整个线程需要阻断不能再执行下去了

我们来看看线程池的

 /**
     * 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.
     *
     * <p>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); // 1. 把线程池的状态设置为 SHUTDOWN
            interruptIdleWorkers(); // 2. 把空闲的工作线程置为中断
            onShutdown(); // 3. 一个空实现,暂不用关注
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

注释可以看到,该方法有序的关闭之前的任务,且不接受新的任务,如果关闭了也不会产生其他影响。
执行shutdown后知道任务数变成0,再进行terminated进行销毁

方式2使用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.
     *
     * <p>This method does not wait for actively executing tasks to
     * terminate.  Use {@link #awaitTermination awaitTermination} to
     * do that.
     *
     * <p>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<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP); // 1:把线程池设置为STOP
            interruptWorkers(); // 2.中断工作线程
            tasks = drainQueue(); // 3.把线程池中的任务都 drain 出来
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

该方法和shutdown不同在于,直接停止所有当前的线程,更暴力一些

应用

在实际引用中,我们在滚动更新、部署项目的时候会使用到关闭线程的方式,当然根据上面的我们也可以知道,类似于在k8s上更新项目,最好的方式当然是shutdown等待所有的线程都执行完之后再把任务结束,从而达到递进式的更新

Leave a Reply

Your email address will not be published. Required fields are marked *