关于高并发:你知道线程池是如何退出程序的吗

4次阅读

共计 4414 个字符,预计需要花费 12 分钟才能阅读完成。

摘要: 本文,咱们就来从源码角度深度解析线程池是如何优雅的退出程序的。

本文分享自华为云社区《【高并发】从源码角度深度解析线程池是如何实现优雅退出的》,作者:冰 河。

本文,咱们就来从源码角度深度解析线程池是如何优雅的退出程序的。首先,咱们来看下 ThreadPoolExecutor 类中的 shutdown() 办法。

shutdown() 办法

当应用线程池的时候,调用了 shutdown() 办法后,线程池就不会再承受新的执行工作了。然而在调用 shutdown() 办法之前放入工作队列中的工作还是要执行的。此办法是非阻塞办法,调用后会立刻返回,并不会期待工作队列中的工作全副执行结束后再返回。咱们看下 shutdown() 办法的源代码,如下所示。

public void shutdown() {
    // 获取线程池的全局锁
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 查看是否有敞开线程池的权限
        checkShutdownAccess();
        // 将以后线程池的状态设置为 SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 中断 Worker 线程
        interruptIdleWorkers();
        // 为 ScheduledThreadPoolExecutor 调用钩子函数
        onShutdown(); // hook for} finally {
        // 开释线程池的全局锁
        mainLock.unlock();}
    // 尝试将状态变为 TERMINATED
    tryTerminate();}

总体来说,shutdown() 办法的代码比较简单,首先查看了是否有权限来敞开线程池,如果有权限,则再次检测是否有中断工作线程的权限,如果没有权限,则会抛出 SecurityException 异样,代码如下所示。

// 查看是否有敞开线程池的权限
checkShutdownAccess();
// 将以后线程池的状态设置为 SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断 Worker 线程
interruptIdleWorkers();

其中,checkShutdownAccess() 办法的实现代码如下所示。

private void checkShutdownAccess() {SecurityManager security = System.getSecurityManager();
    if (security != null) {security.checkPermission(shutdownPerm);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {for (Worker w : workers)
                security.checkAccess(w.thread);
        } finally {mainLock.unlock();
        }
    }
}

对于 checkShutdownAccess() 办法的代码了解起来比较简单,就是检测是否具备敞开线程池的权限,期间应用了线程池的全局锁。

接下来,咱们看 advanceRunState(int) 办法的源代码,如下所示。

private void advanceRunState(int targetState) {for (;;) {int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

advanceRunState(int) 办法的整体逻辑就是:判断以后线程池的状态是否为指定的状态,在 shutdown() 办法中传递的状态是 SHUTDOWN,如果是 SHUTDOWN,则间接返回;如果不是 SHUTDOWN,则将以后线程池的状态设置为 SHUTDOWN。

接下来,咱们看看 showdown() 办法调用的 interruptIdleWorkers() 办法,如下所示。

private void interruptIdleWorkers() {interruptIdleWorkers(false);
}

能够看到,interruptIdleWorkers() 办法调用的是 interruptIdleWorkers(boolean) 办法,持续看 interruptIdleWorkers(boolean) 办法的源代码,如下所示。

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {t.interrupt();
                } catch (SecurityException ignore) { } finally {w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {mainLock.unlock();
    }
}

上述代码的总体逻辑为:获取线程池的全局锁,循环所有的工作线程,检测线程是否被中断,如果没有被中断,并且 Worker 线程取得了锁,则执行线程的中断办法,并开释线程获取到的锁。此时如果 onlyOne 参数为 true,则退出循环。否则,循环所有的工作线程,执行雷同的操作。最终,开释线程池的全局锁。

接下来,咱们看下 shutdownNow() 办法。

shutdownNow() 办法

如果调用了线程池的 shutdownNow() 办法,则线程池不会再承受新的执行工作,也会将工作队列中存在的工作抛弃,正在执行的 Worker 线程也会被立刻中断,同时,办法会立即返回,此办法存在一个返回值,也就是当前任务队列中被抛弃的工作列表。

shutdownNow() 办法的源代码如下所示。

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 查看是否有敞开权限
        checkShutdownAccess();
        // 设置线程池的状态为 STOP
        advanceRunState(STOP);
        // 中断所有的 Worker 线程
        interruptWorkers();
        // 将工作队列中的工作挪动到 tasks 汇合中
        tasks = drainQueue();} finally {mainLock.unlock();
    }
    / 尝试将状态变为 TERMINATED
    tryTerminate();
    // 返回 tasks 汇合
    return tasks;
}

shutdownNow() 办法的源代码的总体逻辑与 shutdown() 办法基本相同,只是 shutdownNow() 办法将线程池的状态设置为 STOP,中断所有的 Worker 线程,并且将工作队列中的所有工作挪动到 tasks 汇合中并返回。

能够看到,shutdownNow() 办法中断所有的线程时,调用了 interruptWorkers() 办法,接下来,咱们就看下 interruptWorkers() 办法的源代码,如下所示。

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {for (Worker w : workers)
            w.interruptIfStarted();} finally {mainLock.unlock();
    }
}

interruptWorkers() 办法的逻辑比较简单,就是取得线程池的全局锁,循环所有的工作线程,顺次中断线程,最初开释线程池的全局锁。

在 interruptWorkers() 办法的外部,实际上调用的是 Worker 类的 interruptIfStarted() 办法来中断线程,咱们看下 Worker 类的 interruptIfStarted() 办法的源代码,如下所示。

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

发现其本质上调用的还是 Thread 类的 interrupt() 办法来中断线程。

awaitTermination(long, TimeUnit) 办法

当线程池调用了 awaitTermination(long, TimeUnit) 办法后,会阻塞调用者所在的线程,直到线程池的状态批改为 TERMINATED 才返回,或者达到了超时工夫返回。接下来,咱们看下 awaitTermination(long, TimeUnit) 办法的源代码,如下所示。

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    // 获取间隔超时工夫残余的时长
    long nanos = unit.toNanos(timeout);
    // 获取 Worker 线程的的全局锁
    final ReentrantLock mainLock = this.mainLock;
    // 加锁
    mainLock.lock();
    try {for (;;) {
            // 以后线程池状态为 TERMINATED 状态,会返回 true
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            // 达到超时工夫,已超时,则返回 false
            if (nanos <= 0)
                return false;
            // 重置间隔超时工夫的残余时长
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
        // 开释锁
        mainLock.unlock();}
}

上述代码的总体逻辑为:首先获取 Worker 线程的独占锁,后在循环判断以后线程池是否曾经是 TERMINATED 状态,如果是则间接返回 true,否则检测是否曾经超时,如果曾经超时,则返回 false。

如果未超时,则重置间隔超时工夫的残余时长。接下来,进入下一轮循环,再次检测以后线程池是否曾经是 TERMINATED 状态,如果是则间接返回 true,否则检测是否曾经超时,如果曾经超时,则返回 false。

如果未超时,则重置间隔超时工夫的残余时长。以此循环,直到线程池的状态变为 TERMINATED 或者曾经超时。

点击关注,第一工夫理解华为云陈腐技术~

正文完
 0