设为首页 加入收藏

TOP

jdk线程池ThreadPoolExecutor优雅停止原理解析(自己动手实现线程池)(二)(三)
2023-07-25 21:33:13 】 浏览:163
Tags:jdk 程池 ThreadPoolExecutor 解析
mainLock; mainLock.lock(); try { for (MyWorker w : workers) { Thread t = w.thread; // 1. t.isInterrupted(),说明当前线程存在中断信号,之前已经被中断了,无需再次中断 // 2. w.tryLock(), runWorker方法中如果工作线程获取到任务开始工作,会先进行Lock加锁 // 则这里的tryLock会加锁失败,返回false。 而返回true的话,就说明当前工作线程是一个idle线程,需要被中断 if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { // tryLock成功时,会将内部state的值设置为1,通过unlock恢复到未加锁的状态 w.unlock(); } } if (onlyOne) { // 参数onlyOne为true,至多只中断一个工作线程 // 即使上面的t.interrupt()没有执行,也在这里跳出循环 break; } } } finally { mainLock.unlock(); } } /** * 单独为jdk的ScheduledThreadPoolExecutor开的一个钩子函数 * 由ScheduledThreadPoolExecutor继承ThreadExecutor时重写(包级别访问权限) * */ void onShutdown() {}
  1. shutdown方法在入口处使用mainLock加锁后,通过checkShutdownAccess检查当前是否有权限访问工作线程(前提是设置了SecurityManager),如果无权限则会抛出SecurityException异常。
  2. 通过advanceRunState方法将线程池状态推进到SHUTDOWN。
  3. 通过interruptIdleWorkers使用中断指令(Thread.interrupt)唤醒所有处于idle状态的工作线程(存在idle状态的工作线程代表着当前工作队列是空的)。
    idle的工作线程在被唤醒后从getTask方法中退出(getTask中对应的退出逻辑在下文中展开),进而退出runWorker方法,最终系统回收掉工作线程占用的各种资源(第一篇博客中runWorker的解析中提到过)。
  4. 调用包级别修饰的钩子函数onShutdown。这一方法是作者专门为同为java.util.concurrent包下的ScheduledThreadPoolExecutor提供的拓展,不在本篇博客中展开。
  5. 前面提到SHUTDOWN状态的线程池在工作线程都全部退出且工作队列为空时会转变为TIDYING状态,因此通过调用tryTerminate方法尝试终止线程池(当前不一定会满足条件,比如调用了shutdown但工作队列还有很多任务等待执行)。
    tryTerminate方法中细节比较多,下文中再展开分析。
shutdownNow方法

shutdownNow方法同样用于关闭线程池,但比shutdown方法更加激进。shutdownNow方法令线程池从RUNNING状态转变为STOP状态,不再接收新任务,而工作队列中未完成的任务会以列表的形式返回给shutdownNow的调用者。

  • shutdown方法在调用后,虽然不再接受新任务,但会等待工作队列中的队列被慢慢消费掉;而shutdownNow并不会等待,而是将当前工作队列中的所有未被捞取执行的剩余任务全部返回给shutdownNow的调用者,并对所有的工作线程(包括非idle的线程)发出中断通知。
  • 这样做的好处是线程池可以更快的进入终止态,而不必等剩余的任务都完成,都返回给用户后也不会丢任务。
    /**
     * 立即关闭线程池(不再接收新任务,工作队列中未完成的任务会以列表的形式返回)
     * @return 当前工作队列中未完成的任务
     * */
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;

        final ReentrantLock mainLock = this.mainLock;

        // shutdown操作中涉及大量的资源访问和更新,直接通过互斥锁防并发
        mainLock.lock();
        try {
            // 用于shutdown/shutdownNow时的安全访问权限
            checkShutdownAccess();
            // 将线程池状态从RUNNING推进到STOP
            advanceRunState(STOP);
            interruptWorkers();

            // 将工作队列中未完成的任务提取出来(会清空线程池的workQueue)
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }

        // 尝试终止线程池
        tryTerminate();
        return tasks;
    }

   /**
    * shutdownNow方法内,立即终止线程池时该方法被调用
    * 中断通知所有已经启动的工作线程(比如等待在工作队列上的idle工作线程,或者run方法内部await、sleep等,令其抛出中断异常快速结束)
    * */
    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (MyWorker w : workers) {
              // 遍历所有的worker线程,已启动的工作线程全部调用Thread.interrupt方法,发出中断信号
              w.interruptIfStarted();
            }
        } finally {
            mainLock.unlock();
        }
    }

   /**
    * 将工作队列中的任务全部转移出来
    * 用于shutdownNow紧急关闭线程池时将未完成的任务返回给调用者,避免任务丢失
    * */
   private List<Runnable> drainQueue() {
        BlockingQueue<Runnable> queue = this.workQueue;
        ArrayList<Runnable> taskList = new ArrayList<>();
        queue.drainTo(taskList);
        // 通常情况下,普通的阻塞队列的drainTo方法可以一次性的把所有元素都转移到taskList中
        // 但jdk的DelayedQueue或者一些自定义的阻塞队列,drainTo方法无法转移所有的元素
        // (比如DelayedQueue的drainTo方法只能转移已经不需要延迟的元素,即getDelay()<=0)
        if (!queue.isEmpty()) {
           // 所以在这里打一个补丁逻辑:如果drainTo方法执行后工作队列依然不为空,则通过更基础的remove方法把队列中剩余元素一个一个的循环放到taskList中
           for (Runnable r : queue.toArray(new Runnable[0])) {
              if (q
首页 上一页 1 2 3 4 5 6 7 下一页 尾页 3/7/7
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇数据库可视化工具分享 (DBeaver) 下一篇Spring AOP中增强Advice的执行顺序

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目