首页 > 编程语言 > 面试官不按套路,竟然问我Java线程池是怎么统计线程空闲时间?

面试官不按套路,竟然问我Java线程池是怎么统计线程空闲时间?

时间:2022-11-18 10:24:16浏览次数:47  
标签:面试官 Java thread Thread 一灯 线程 java ThreadPoolExecutor

背景介绍:

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

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

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

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

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

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

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

我: 线程池复用线程的逻辑很简单,就是在线程启动后,通过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 关注公众号:一灯架构
捕获到异常:抛异常了!

面试官: 小伙子,论源码,还是得看你,还是你背的熟。现在我就给你发offer,薪资直接涨10%,明天9点就来上班吧,咱们公司实行996工作制。

我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见

image

标签:面试官,Java,thread,Thread,一灯,线程,java,ThreadPoolExecutor
From: https://www.cnblogs.com/yidengjiagou/p/16902327.html

相关文章

  • python单线程爬虫安装与调试
    信息时代的到来,带给我们海量信息的同时也给我们带来很多有用的价值。如何在这些海量信息池里面找到自己需要的有价值的信息就离不开爬虫技术了,那么在python下如果去部署安装......
  • vscode搭建简单java运行环境
    打开官网​​​https://code.visualstudio.com/docs/languages/java​​安装这几个就够了(好像也可以通过安装JavaExtensionPack这个插件来安装上面的几个)正常安装能get到......
  • 力扣704(java&python)-二分查找(简单)
    题目:给定一个 n 个元素有序的(升序)整型数组 nums和一个目标值 target ,写一个函数搜索 nums 中的target,如果目标值存在返回下标,否则返回-1。示例1:输入:nums......
  • JavaScript_语法_一元运算符与JavaScript_语法_算数&比较运算符
    JavaScript_语法_一元运算符运算符:1.一元运算符:只有一个运算数的运算符++、--、+(正号)、-(负号)......
  • JavaCV音视频开发宝典:vb8和vp9编码的webm格式视频文件转成mp4文件
    本文转载自:https://blog.csdn.net/eguid_1/article/details/125251492《JavaCV音视频开发宝典》专栏目录导航《JavaCV音视频开发宝典》专栏介绍和目录​前言mp4不用......
  • Java list stream 排序
    排序参考:https://blog.csdn.net/weixin_41405524/article/details/125524134Liststream方法用法参考:https://blog.csdn.net/BHSZZY/article/details/122860048......
  • Java新特性(2):Java 10以后
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ 虽然到目前为止Java的版本更新还没有什么惊天动地的改变,但总是会冒出一些有趣的小玩意。前面列举了Java9和Java10的一些......
  • JUC学习笔记——并发工具线程池
    JUC学习笔记——并发工具线程池在本系列内容中我们会对JUC做一个系统的学习,本片将会介绍JUC的并发工具线程池我们会分为以下几部分进行介绍:线程池介绍自定义线程池模......
  • lombok,快速生成java bean
    在pom里加个依赖就行  然后在javabean那里加@Data注解  done!......
  • 单线程与多线程的区别及是否有必要多线程的理解
    单线程是一个主线程,执行main方法。多线程是1个主线程,带多个子线程,可以同步进行,也可以异步进行。我个人认为单线程是1种理想的方式,对程序而已。实际上多线程才是真实的线......