详解Java线程池如何统计线程空闲时间

背景介绍

你刚从学校毕业后,到新公司实习,试用期又被毕业,然后你又不得不出来面试,好在面试的时候碰到个美女面试官!

面试官: 小伙子,我看你简历上写的项目中用到了线程池,你知道线程池是怎样实现复用线程的?

这面试官是不是想坑我?是不是摆明了不让我通过?

难道你不应该问线程池有哪些核心参数?每个参数具体作用是什么?

往线程池中不断提交任务,线程池的处理流程是什么?

这些才是你应该问的,这些八股文我已经背熟了,你不问,瞎问什么复用线程?

幸亏我看了一灯的八股文,听我给你背一遍!

我: 线程池复用线程的逻辑很简单,就是在线程启动后,通过while死循环,不断从阻塞队列中拉取任务,从而达到了复用线程的目的。

具体源码如下:

// 线程执行入口
public void run() {
    runWorker(this);
}

// 线程运行核心方法
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();
    boolean completedAbruptly = true;
    try {
        // 1. 使用while死循环,不断从阻塞队列中拉取任务
        while (task != null || (task = getTask()) != null) {
            // 加锁,保证thread不被其他线程中断(除非线程池被中断)
            w.lock();
            // 2. 校验线程池状态,是否需要中断当前线程
            if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                            runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 3. 执行run方法
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x;
                    throw x;
                } catch (Error x) {
                    thrown = x;
                    throw x;
                } catch (Throwable x) {
                    thrown = x;
                    throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

runWorker方法逻辑很简单,就是不断从阻塞队列中拉取任务并执行。

面试官: 小伙子,有点东西。我们都知道线程池会回收超过空闲时间的线程,那么线程池是怎么统计线程的空闲时间的?

美女面试官的问题真刁钻,让人头疼啊!这问的也太深了吧!

没看过源码的话,真不好回答。

我: 嗯...,可能是有个监控线程在后台不停的统计每个线程的空闲时间,看到线程的空闲时间超过阈值的时候,就回收掉。

面试官: 小伙子,你的想法挺不错,逻辑很严谨,你确定线程池内部是这么实现的吗?

问得我有点不自信了,没看过源码不能瞎蒙。

我还是去瞅一眼一灯写的八股文吧。

我: 这个我知道,线程池统计线程的空闲时间的实现逻辑很简单。

阻塞队列(BlockingQueue)提供了一个poll(time, unit)方法用来拉取数据,

作用就是: 当队列为空时,会阻塞指定时间,然后返回null。

线程池就是就是利用阻塞队列的这个方法,如果在指定时间内拉取不到任务,就表示该线程的存活时间已经超过阈值了,就要被回收了。

具体源码如下:

// 从阻塞队列中拉取任务
private Runnable getTask() {
    boolean timedOut = false;
    for (; ; ) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 1. 如果线程池已经停了,或者阻塞队列是空,就回收当前线程
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        // 2. 再次判断是否需要回收线程
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
            // 3. 在指定时间内,从阻塞队列中拉取任务
            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
            if (r != null)
                return r;
          	// 4. 如果没有拉取到任务,就标识该线程已超时,然后就被回收
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

面试官: 小伙子,可以啊,你是懂线程池源码的。再问你个问题,如果线程池抛异常了,也没有try/catch,会发生什么?

美女面试官你这是准备打破砂锅问到底,铁了心不让我过,是吧?

我的代码风格是很严谨的,谁写的业务代码不try/catch,也没遇到过这种情况。

让我再看一下一灯总结的八股文吧。

我: 有了,线程池中的代码如果抛异常了,也没有try/catch,会从线程池中删除这个异常线程,并创建一个新线程。

不信的话,我们可以测试验证一下:

/**
 * @author 一灯架构
 * @apiNote 线程池示例
 **/
public class ThreadPoolDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        // 1. 创建一个单个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // 2. 往线程池中提交3个任务
        for (int i = 0; i < 3; i++) {
            executorService.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 关注公众号:一灯架构");
                throw new RuntimeException("抛异常了!");
            });
        }

        // 3. 关闭线程池
        executorService.shutdown();
    }

}

输出结果:

pool-1-thread-1 关注公众号:一灯架构
pool-1-thread-2 关注公众号:一灯架构
pool-1-thread-3 关注公众号:一灯架构
Exception in thread "pool-1-thread-1" java.lang.RuntimeException: 抛异常了!
    at com.yideng.SynchronousQueueDemo.lambda$main$0(ThreadPoolDemo.java:21)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-2" java.lang.RuntimeException: 抛异常了!
    at com.yideng.SynchronousQueueDemo.lambda$main$0(ThreadPoolDemo.java:21)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-3" java.lang.RuntimeException: 抛异常了!
    at com.yideng.SynchronousQueueDemo.lambda$main$0(ThreadPoolDemo.java:21)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

从输出结果中可以看出,线程名称并不是同一个,而是累加的,说明原线程已经被回收,新建了个线程。

我们再看一下源码,验证一下:

// 线程抛异常后,退出逻辑
private void processWorkerExit(ThreadPoolExecutor.Worker w, boolean completedAbruptly) {
    if (completedAbruptly)
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        // 1. 从工作线程中删除当前线程
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    // 2. 中断当前线程
    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && !workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 3. 新建一个线程
        addWorker(null, false);
    }
}

如果想统一处理异常,可以自定义线程创建工厂,在工厂里面设置异常处理逻辑。

/**
 * @author 一灯架构
 * @apiNote 线程池示例
 **/
public class ThreadPoolDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        // 1. 创建一个单个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor(runnable -> {
            // 2. 自定义线程创建工厂,并设置异常处理逻辑
            Thread thread = new Thread(runnable);
            thread.setUncaughtExceptionHandler((t, e) -> {
                System.out.println("捕获到异常:" + e.getMessage());
            });
            return thread;
        });

        // 3. 往线程池中提交3个任务
        for (int i = 0; i < 3; i++) {
            executorService.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 关注公众号:一灯架构");
                throw new RuntimeException("抛异常了!");
            });
        }

        // 4. 关闭线程池
        executorService.shutdown();
    }

}

输出结果:

Thread-0 关注公众号:一灯架构
捕获到异常:抛异常了!
Thread-1 关注公众号:一灯架构
捕获到异常:抛异常了!
Thread-2 关注公众号:一灯架构
捕获到异常:抛异常了!

以上就是详解Java线程池如何统计线程空闲时间的详细内容,更多关于Java统计线程空闲时间的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java中的异步与线程池解读

    目录 初始化线程的4种方式 1.继承Thread 2.实现Runnable 接口 3.实现Callable 接口+ FutureTask (可以拿到返回结果,可以处理异常) 4.线程池 创建线程池(ExecutorService) 1.Executors 工具类创建 2.原生方法创建线程池 3.线程池的运行流程 线程池创建 4. 四种常见的线程池 为什么要使用线程池 CompletableFuture 异步编排 1.创建异步对象 2.计算完成时(线程执行成功)回调方法 3.handle 方法(可

  • 详解Java线程池队列中的延迟队列DelayQueue

    目录 DelayQueue延迟队列 DelayQueue使用场景 DelayQueue属性 DelayQueue构造方法 实现Delayed接口使用示例 DelayQueue总结 在阻塞队里中,除了对元素进行增加和删除外,我们可以把元素的删除做一个延迟的处理,即使用DelayQueue的方法.本文就来和大家聊聊Java线程池队列中的DelayQueue—延迟队列 public enum QueueTypeEnum { ARRAY_BLOCKING_QUEUE(1, "ArrayBlockingQ

  • Java手写线程池之向JDK线程池进发

    目录 前言 JDK线程池一瞥 自己动手实现线程池 线程池参数介绍 实现Runnable 实现Callable 拒绝策略的实现 线程池关闭实现 工作线程的工作实现 线程池实现的BUG 完整代码 线程池测试 总结 前言 在前面的文章自己动手写乞丐版线程池中,我们写了一个非常简单的线程池实现,这个只是一个非常简单的实现,在本篇文章当中我们将要实现一个和JDK内部实现的线程池非常相似的线程池. JDK线程池一瞥 我们首先看一个JDK给我们提供的线程池ThreadPoolExecutor的构造函数的参数:

  • java 线程池的实现原理、优点与风险、以及4种线程池实现

    为什么需要线程池 我们有两种常见的创建线程的方法,一种是继承Thread类,一种是实现Runnable的接口,Thread类其实也是实现了Runnable接口.但是我们创建这两种线程在运行结束后都会被虚拟机销毁,如果线程数量多的话,频繁的创建和销毁线程会大大浪费时间和效率,更重要的是浪费内存.那么有没有一种方法能让线程运行完后不立即销毁,而是让线程重复使用,继续执行其他的任务哪? 这就是线程池的由来,很好的解决线程的重复利用,避免重复开销. 线程池的优点 1.线程是稀缺资源,使用线程池可以减少创

  • 一文带你弄懂Java中线程池的原理

    目录 为什么要用线程池 线程池的原理 ThreadPoolExecutor提供的构造方法 ThreadPoolExecutor的策略 线程池主要的任务处理流程 ThreadPoolExecutor如何做到线程复用的 四种常见的线程池 newCachedThreadPool newFixedThreadPool newSingleThreadExecutor newScheduledThreadPool 小结 在工作中,我们经常使用线程池,但是你真的了解线程池的原理吗?同时,线程池工作原理和底层实

  • Java实现手写一个线程池的示例代码

    目录 概述 线程池框架设计 代码实现 阻塞队列的实现 线程池消费端实现 获取任务超时设计 拒绝策略设计 概述 线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和细节吗?如果直接去看jdk源码的话,可能有一定的难度,那么我们可以先通过手写一个简单的线程池框架,去掌握线程池的基本原理后,再去看jdk的线程池源码就会相对容易,而且不容易忘记. 线程池框架设计 我们都知道,线程资源的创建和销毁并不是没有代价的,甚至开销是非常高的.同

  • Java实现手写乞丐版线程池的示例代码

    目录 前言 线程池的具体实现 线程池实现思路 线程池实现代码 线程池测试代码 杂谈 总结 前言 在上篇文章线程池的前世今生当中我们介绍了实现线程池的原理,在这篇文章当中我们主要介绍实现一个非常简易版的线程池,深入的去理解其中的原理,麻雀虽小,五脏俱全. 线程池的具体实现 线程池实现思路 任务保存到哪里? 在上篇文章线程池的前世今生当中我们具体去介绍了线程池当中的原理.在线程池当中我们有很多个线程不断的从任务池(用户在使用线程池的时候不断的使用execute方法将任务添加到线程池当中)里面去拿任务

  • 一文了解Java 线程池的正确使用姿势

    目录 概述 线程池介绍 线程池创建 ThreadPoolExecutor创建 Executors创建 newFixedThreadPool newCachedThreadPool newSingleThreadExecutor newScheduledThreadPool newWorkStealingPool 线程池关键API和例子 提交执行任务API 关闭线程池API 线程池监控API 扩展API 使用注意事项 概述 线程池在平时的工作中出场率非常高,基本大家多多少少都要了解过,可能不是很全

  • 一篇文章带你搞懂Java线程池实现原理

    目录 1. 为什么要使用线程池 2. 线程池的使用 3. 线程池核心参数 4. 线程池工作原理 5. 线程池源码剖析 5.1 线程池的属性 5.2 线程池状态 5.3 execute源码 5.4 worker源码 5.5 runWorker源码 1. 为什么要使用线程池 使用线程池通常由以下两个原因: 频繁创建销毁线程需要消耗系统资源,使用线程池可以复用线程. 使用线程池可以更容易管理线程,线程池可以动态管理线程个数.具有阻塞队列.定时周期执行任务.环境隔离等. 2. 线程池的使用 /** *

  • 详解Java线程池如何统计线程空闲时间

    背景介绍 你刚从学校毕业后,到新公司实习,试用期又被毕业,然后你又不得不出来面试,好在面试的时候碰到个美女面试官! 面试官: 小伙子,我看你简历上写的项目中用到了线程池,你知道线程池是怎样实现复用线程的? 这面试官是不是想坑我?是不是摆明了不让我通过? 难道你不应该问线程池有哪些核心参数?每个参数具体作用是什么? 往线程池中不断提交任务,线程池的处理流程是什么? 这些才是你应该问的,这些八股文我已经背熟了,你不问,瞎问什么复用线程? 幸亏我看了一灯的八股文,听我给你背一遍! 我: 线程池复用线程

  • 详解Java数据库连接池

    一.什么是数据库连接池 就是一个容器持有多个数据库连接,当程序需要操作数据库的时候直接从池中取出连接,使用完之后再还回去,和线程池一个道理. 二.为什么需要连接池,好处是什么? 1.节省资源,如果每次访问数据库都创建新的连接,创建和销毁都浪费系统资源 2.响应性更好,省去了创建的时间,响应性更好. 3.统一管理数据库连接,避免因为业务的膨胀导致数据库连接的无限增多. 4.便于监控. 三.都有哪些连接池方案 数据库连接池的方案有不少,我接触过的连接池方案有: 1.C3p0 这个连接池我很久之前看到

  • 详解JAVA 常量池

    前言 对常量池的理解之前,需要熟悉的是一些术语: 字面量 在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation). 几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数.浮点数以及字符串:而有很多也对布尔类型和字符类型的值也支持字面量表示: 还有一些甚至对枚举类型的元素以及像数组.记录和对象等复合类型的值也支持字面量表示法.C语言关于复合字面量的介绍可参考: [1]  . 百度也给了一个例子: 这个object-c 的例子,容易理解. #incl

  • 详解Java多线程编程中的线程同步方法

    1.多线程的同步: 1.1.同步机制: 在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生.所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问. 1.2.共享成员变量的例子: 成员变量与局部变量: 成员变量: 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的. 局部变量: 如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝.他们

  • 详解Java线程池和Executor原理的分析

    详解Java线程池和Executor原理的分析 线程池作用与基本知识 在开始之前,我们先来讨论下"线程池"这个概念."线程池",顾名思义就是一个线程缓存.它是一个或者多个线程的集合,用户可以把需要执行的任务简单地扔给线程池,而不用过多的纠结与执行的细节.那么线程池有哪些作用?或者说与直接用Thread相比,有什么优势?我简单总结了以下几点: 减小线程创建和销毁带来的消耗 对于Java Thread的实现,我在前面的一篇blog中进行了分析.Java Thread与内

  • 详解Java中的线程池

    1.简介 使用线程池可以避免线程的频繁创建以及销毁. JAVA中提供的用于实现线程池的API: Executor.ExecutorService.AbstractExecutorService.ThreadPoolExecutor.ForkJoinPool都位于java.util.concurrent包下. *ThreadPoolExecutor.ForkJoinPool为线程池的实现类. 2.Executor public interface Executor { /** * 向线程池提交一个

  • 详解Java线程池的使用及工作原理

    一.什么是线程池? 线程池是一种用于实现计算机程序并发执行的软件设计模式.线程池维护多个线程,等待由调度程序分配任务以并发执行,该模型提高了性能,并避免了由于为短期任务频繁创建和销毁线程而导致的执行延迟. 二.线程池要解决什么问题? 说到线程池就一定要从线程的生命周期讲起. 从图中可以了解无论任务执行多久,每个线程都要经历从生到死的状态.而使用线程池就是为了避免线程的重复创建,从而节省了线程的New至Runnable, Running至Terminated的时间:同时也会复用线程,最小化的节省系

  • 详解Java线程池是如何重复利用空闲线程的

    在Java开发中,经常需要创建线程去执行一些任务,实现起来也非常方便,但如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间.此时,我们很自然会想到使用线程池来解决这个问题. 使用线程池的好处: 降低资源消耗.java中所有的池化技术都有一个好处,就是通过复用池中的对象,降低系统资源消耗.设想一下如果我们有n多个子任务需要执行,如果我们为每个子任务都创建一个执行线程,而创建线程的过程是需要一定的系统消耗

  • 详解Java如何关闭线程以及线程池

    目录 前言 1. 关闭线程 1.1 volatile关键字 1.2 intrrrupt()方法 2.关闭线程池 2.1 shutdownNow()方法 2.2 shutdown()方法 前言 这个问题是一个高频的面试题 而且在印象中是由stop方法执行或者终端中的kill杀死 但是这些方法直接简单粗暴,很不安全,而且也不推广 不使用stop的方法 之所以不安全不推广是因为: stop方法不管线程逻辑是否完整,都会终止当前正在运行的线程 会破坏其原子逻辑(多线程加了锁之解决资源共享,但是stop会

  • 详解Java线程池如何实现优雅退出

    目录 shutdown()方法 shutdownNow()方法 awaitTermination(long, TimeUnit)方法 在[高并发专题]中,我们从源码角度深度分析了线程池中那些重要的接口和抽象类.深度解析了线程池是如何创建的,ThreadPoolExecutor类有哪些属性和内部类,以及它们对线程池的重要作用.深度分析了线程池的整体核心流程,以及如何拆解Worker线程的执行代码,深度解析Worker线程的执行流程. 本文,我们就来从源码角度深度解析线程池是如何优雅的退出程序的.首

随机推荐