java线程池使用及原理面试题

目录
  • 引导语
  • 1、说说你对线程池的理解?
  • 2、ThreadPoolExecutor、Executor、ExecutorService、Runnable、Callable、FutureTask 之间的关系?
  • 3、说一说队列在线程池中起的作用?
  • 4、结合请求不断增加时,说一说线程池构造器参数的含义和表现?
  • 5、coreSize 和 maxSize 可以动态设置么,有没有规则限制?
  • 6、说一说对于线程空闲回收的理解,源码中如何体现的?
  • 7、如果我想在线程池任务执行之前和之后,做一些资源清理的工作,可以么,如何做?
  • 8、线程池中的线程创建,拒绝请求可以自定义实现么?如何自定义?
  • 9、说说你对 Worker 的理解?
  • 10、说一说 submit 方法执行的过程?
  • 11、说一说线程执行任务之后,都在干啥?
  • 12、keepAliveTime 设置成负数或者是 0,表示无限阻塞?
  • 13、说一说 Future.get 方法是如何拿到线程的执行结果的?
  • 14、总结

引导语

线程池在日常面试中占比很大,主要是因为线程池内容涉及的知识点较广,比如涉及到队列、线程、锁等等,所以很多面试官喜欢把线程池作为问题的起点,然后延伸到其它内容,由于我们专栏已经说过队列、线程、锁面试题了,所以本章面试题还是以线程池为主。

1、说说你对线程池的理解?

答:答题思路从大到小,从全面到局部,总的可以这么说,线程池结合了锁、线程、队列等元素,在请求量较大的环境下,可以多线程的处理请求,充分的利用了系统的资源,提高了处理请求的速度,细节可以从以下几个方面阐述:

  • ThreadPoolExecutor 类结构;
  • ThreadPoolExecutor coreSize、maxSize 等重要属性;
  • Worker 的重要作用;
  • submit 的整个过程。

通过以上总分的描述,应该可以说清楚对线程池的理解了,如果是面对面面试的话,可以边说边画出线程池的整体架构图(见《ThreadPoolExecutor 源码解析》)。

2、ThreadPoolExecutor、Executor、ExecutorService、Runnable、Callable、FutureTask 之间的关系?

答:以上 6 个类可以分成两大类:一种是定义任务类,一种是执行任务类。

定义任务类:Runnable、Callable、FutureTask。Runnable 是定义无返回值的任务,Callable 是定义有返回值的任务,FutureTask 是对 Runnable 和 Callable 两种任务的统一,并增加了对任务的管理功能;

执行任务类:ThreadPoolExecutor、Executor、ExecutorService。Executor 定义最基本的运行接口,ExecutorService 是对其功能的补充,ThreadPoolExecutor 提供真正可运行的线程池类,三个类定义了任务的运行机制。

日常的做法都是先根据定义任务类定义出任务来,然后丢给执行任务类去执行。

3、说一说队列在线程池中起的作用?

答:作用如下:

当请求数大于 coreSize 时,可以让任务在队列中排队,让线程池中的线程慢慢的消费请求,实际工作中,实际线程数不可能等于请求数,队列提供了一种机制让任务可排队,起一个缓冲区的作用;

当线程消费完所有的线程后,会阻塞的从队列中拿数据,通过队列阻塞的功能,使线程不消亡,一旦队列中有数据产生后,可立马被消费。

4、结合请求不断增加时,说一说线程池构造器参数的含义和表现?

答:线程池构造器各个参数的含义如下:

coreSize 核心线程数;

maxSize 最大线程数;

keepAliveTime 线程空闲的最大时间;

queue 有多种队列可供选择,比如:1:SynchronousQueue,为了避免任务被拒绝,要求线程池的 maxSize 无界,缺点是当任务提交的速度超过消费的速度时,可能出现无限制的线程增长;2:LinkedBlockingQueue,无界队列,未消费的任务可以在队列中等待;3:ArrayBlockingQueue,有界队列,可以防止资源被耗尽;

线程新建的 ThreadFactory 可以自定义,也可以使用默认的 DefaultThreadFactory,DefaultThreadFactory 创建线程时,优先级会被限制成 NORM_PRIORITY,默认会被设置成非守护线程;

在 Executor 已经关闭或对最大线程和最大队列都使用饱和时,可以使用 RejectedExecutionHandler 类进行异常捕捉,有如下四种处理策略:ThreadPoolExecutor.AbortPolicy、ThreadPoolExecutor.DiscardPolicy、ThreadPoolExecutor.CallerRunsPolicy、ThreadPoolExecutor.DiscardOldestPolicy。

当请求不断增加时,各个参数起的作用如下:

请求数 < coreSize:创建新的线程来处理任务;

coreSize <= 请求数 && 能够成功入队列:任务进入到队列中等待被消费;

队列已满 && 请求数 < maxSize:创建新的线程来处理任务;

队列已满 && 请求数 >= maxSize:使用 RejectedExecutionHandler 类拒绝请求。

5、coreSize 和 maxSize 可以动态设置么,有没有规则限制?

答:一般来说,coreSize 和 maxSize 在线程池初始化时就已经设定了,但我们也可以通过 setCorePoolSize、setMaximumPoolSize 方法动态的修改这两个值。

setCorePoolSize 的限制见如下源码:

// 如果新设置的值小于 coreSize,多余的线程在空闲时会被回收(不保证一定可以回收成功)
// 如果大于 coseSize,会新创建线程
public void setCorePoolSize(int corePoolSize) {
    if (corePoolSize < 0)
        throw new IllegalArgumentException();
    int delta = corePoolSize - this.corePoolSize;
    this.corePoolSize = corePoolSize;
    // 活动的线程大于新设置的核心线程数
    if (workerCountOf(ctl.get()) > corePoolSize)
        // 尝试将可以获得锁的 worker 中断,只会循环一次
        // 最后并不能保证活动的线程数一定小于核心线程数
        interruptIdleWorkers();
    // 设置的核心线程数大于原来的核心线程数
    else if (delta > 0) {
        // 并不清楚应该新增多少线程,取新增核心线程数和等待队列数据的最小值,够用就好
        int k = Math.min(delta, workQueue.size());
        // 新增线程直到k,如果期间等待队列空了也不会再新增
        while (k-- > 0 && addWorker(null, true)) {
            if (workQueue.isEmpty())
                break;
        }
    }
}

setMaximumPoolSize 的限制见如下源码:

// 如果 maxSize 大于原来的值,直接设置。
// 如果 maxSize 小于原来的值,尝试干掉一些 worker
public void setMaximumPoolSize(int maximumPoolSize) {
    if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
        throw new IllegalArgumentException();
    this.maximumPoolSize = maximumPoolSize;
    if (workerCountOf(ctl.get()) > maximumPoolSize)
        interruptIdleWorkers();
}

6、说一说对于线程空闲回收的理解,源码中如何体现的?

答:空闲线程回收的时机:如果线程超过 keepAliveTime 时间后,还从阻塞队列中拿不到任务(这种情况我们称为线程空闲),当前线程就会被回收,如果 allowCoreThreadTimeOut 设置成 true,core thread 也会被回收,直到还剩下一个线程为止,如果 allowCoreThreadTimeOut 设置成 false,只会回收非 core thread 的线程。

线程在任务执行完成之后,之所有没有消亡,是因为阻塞的从队列中拿任务,在 keepAliveTime 时间后都没有拿到任务的话,就会打断阻塞,线程直接返回,线程的生命周期就结束了,JVM 会回收掉该线程对象,所以我们说的线程回收源码体现就是让线程不在队列中阻塞,直接返回了,可以见 ThreadPoolExecutor 源码解析章节第三小节的源码解析。

7、如果我想在线程池任务执行之前和之后,做一些资源清理的工作,可以么,如何做?

答:可以的,ThreadPoolExecutor 提供了一些钩子函数,我们只需要继承 ThreadPoolExecutor 并实现这些钩子函数即可。在线程池任务执行之前实现 beforeExecute 方法,执行之后实现 afterExecute 方法。

8、线程池中的线程创建,拒绝请求可以自定义实现么?如何自定义?

答:可以自定义的,线程创建默认使用的是 DefaultThreadFactory,自定义话的只需要实现 ThreadFactory 接口即可;拒绝请求也是可以自定义的,实现 RejectedExecutionHandler 接口即可;在 ThreadPoolExecutor 初始化时,将两个自定义类作为构造器的入参传递给 ThreadPoolExecutor 即可。

9、说说你对 Worker 的理解?

答:详见《ThreadPoolExecutor 源码解析》中 1.4 小节。

10、说一说 submit 方法执行的过程?

答:详见《ThreadPoolExecutor 源码解析》中 2 小节。

11、说一说线程执行任务之后,都在干啥?

答:线程执行任务完成之后,有两种结果:

线程会阻塞从队列中拿任务,没有任务的话无限阻塞;线程会阻塞从队列中拿任务,没有任务的话阻塞一段时间后,线程返回,被 JVM 回收。

12、keepAliveTime 设置成负数或者是 0,表示无限阻塞?

答:这种是不对的,如果 keepAliveTime 设置成负数,在线程池初始化时,就会直接报 IllegalArgumentException 的异常,而设置成 0,队列如果是 LinkedBlockingQueue 的话,执行 workQueue.poll (keepAliveTime, TimeUnit.NANOSECONDS) 方法时,如果队列中没有任务,会直接返回 null,导致线程立马返回,不会无限阻塞。

如果想无限阻塞的话,可以把 keepAliveTime 设置的很大,把 TimeUnit 也设置的很大,接近于无限阻塞。

13、说一说 Future.get 方法是如何拿到线程的执行结果的?

答:我们需要明确几点:

submit 方法的返回结果实际上是 FutureTask,我们平时都是针对接口编程,所以使用的是 Future.get 来拿到线程的执行结果,实际上是 FutureTask.get ,其方法底层是从 FutureTask 的 outcome 属性拿值的;《ThreadPoolExecutor 源码解析》中 2 小节中详细说明了 submit 方法最终会把线程的执行结果赋值给 outcome。

结合 1、2,当线程执行完成之后,自然就可以从 FutureTask 的 outcome 属性中拿到值。

14、总结

如果我们弄清楚 ThreadPoolExecutor 的原理之后,线程池的面试题都很简单,所以建议大家多看看 《ThreadPoolExecutor 源码解析》这小节。

以上就是java线程池使用及原理面试题的详细内容,更多关于java线程池面试题的资料请关注我们其它相关文章!

(0)

相关推荐

  • java线程池不同场景如何使用及示例经验总结

    https://blog.csdn.net/qq_34272760/article/details/120648262?spm=1001.2014.3001.5502

  • Java自定义线程池的实现示例

    目录 一.Java语言本身也是多线程,回顾Java创建线程方式如下: 二.JDK线程池工具类. 三.业界知名自定义线程池扩展使用. 一.Java语言本身也是多线程,回顾Java创建线程方式如下: 1.继承Thread类,(Thread类实现Runnable接口),来个类图加深印象. 2.实现Runnable接口实现无返回值.实现run()方法,啥时候run,黑话了. 3.实现Callable接口重写call()+FutureTask获取. public class CustomThread {

  • 非常适合新手学生的Java线程池超详细分析

    目录 线程池的好处 创建线程池的五种方式 缓存线程池CachedThreadPool 固定容量线程池FixedThreadPool 单个线程池SingleThreadExecutor 定时任务线程池ScheduledThreadPool ThreadPoolExecutor创建线程池(十分推荐) ThreadPoolExecutor的七个参数详解 workQueue handler 如何触发拒绝策略和线程池扩容? 线程池的好处 可以实现线程的复用,避免重新创建线程和销毁线程.创建线程和销毁线程对

  • 学生视角手把手带你写Java 线程池

    目录 Java手写线程池(第一代) 手写线程池-定义参数 手写线程池-构造器 手写线程池-默认构造器 手写线程池-execute方法 手写线程池-处理任务 手写线程池-优雅关闭线程池 手写线程池-暴力关闭线程池 手写线程池-源代码 问题 Java手写线程池(第一代) 经常使用线程池,故今天突发奇想,手写一个线程池,会有很多不足,请多多宽容.因为这也是第一代的版本,后续会更完善. 手写线程池-定义参数 private final AtomicInteger taskcount=new Atomic

  • Java线程池7个参数的详细含义

    目录 一.corePoolSize线程池核心线程大小 二.maximumPoolSize线程池最大线程数量 三.keepAliveTime空闲线程存活时间 四.unit空闲线程存活时间单位 五.workQueue工作队列 六.threadFactory线程工厂 七.handler拒绝策略 java多线程开发时,常常用到线程池技术,这篇文章是对创建java线程池时的七个参数的详细解释. 从源码中可以看出,线程池的构造函数有7个参数 这 7 个参数分别是: corePoolSize:核心线程数. m

  • Java线程池实现原理总结

    目录 一.线程池参数 二.线程池执行流程 三.四种现成的线程池 要理解实现原理,必须把线程池的几个参数彻底搞懂,不要死记硬背 一.线程池参数 1.corePoolSize(必填):核心线程数. 2.maximumPoolSize(必填):最大线程数. 3.keepAliveTime(必填):线程空闲时长.如果超过该时长,非核心线程就会被回收. 4.unit(必填):指定keepAliveTime的时间单位.常用的有:TimeUnit.MILLISECONDS(毫秒).TimeUnit.SECON

  • 学生视角手把手带你写Java 线程池改良版

    目录 Java手写线程池(第二代) 第二代线程池的优化 线程池构造器 线程池拒绝策略 execute方法 手写线程池源码 MyExecutorService MyRejectedExecutionException MyRejectedExecutionHandle 核心类MyThreadPoolExecutor 线程池测试类 Java手写线程池(第二代) 第二代线程池的优化 1:新增了4种拒绝策略.分别为:MyAbortPolicy.MyDiscardPolicy.MyDiscardOldes

  • Java线程池必知必会知识点总结

    目录 1.线程数使用开发规约 2.ThreadPoolExecutor源码 1.构造函数 2.核心参数 3.execute()方法 3.线程池的工作流程 4.Executors创建返回ThreadPoolExecutor对象(不推荐) 1.Executors#newCachedThreadPool=>创建可缓存的线程池 2.Executors#newSingleThreadExecutor=>创建单线程的线程池 3.Executors#newFixedThreadPool=>创建固定长度

  • java线程池使用及原理面试题

    目录 引导语 1.说说你对线程池的理解? 2.ThreadPoolExecutor.Executor.ExecutorService.Runnable.Callable.FutureTask 之间的关系? 3.说一说队列在线程池中起的作用? 4.结合请求不断增加时,说一说线程池构造器参数的含义和表现? 5.coreSize 和 maxSize 可以动态设置么,有没有规则限制? 6.说一说对于线程空闲回收的理解,源码中如何体现的? 7.如果我想在线程池任务执行之前和之后,做一些资源清理的工作,可以

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

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

  • Java线程池FutureTask实现原理详解

    前言 线程池可以并发执行多个任务,有些时候,我们可能想要跟踪任务的执行结果,甚至在一定时间内,如果任务没有执行完成,我们可能还想要取消任务的执行,为了支持这一特性,ThreadPoolExecutor提供了 FutureTask 用于追踪任务的执行和取消.本篇介绍FutureTask的实现原理. 类视图 为了更好的理解FutureTask的实现原理,这里先提供几个重要接口和类的结构,如下图所示: RunnableAdapter ThreadPoolExecutor提供了submit接口用于提交任

  • 深度源码解析Java 线程池的实现原理

    java 系统的运行归根到底是程序的运行,程序的运行归根到底是代码的执行,代码的执行归根到底是虚拟机的执行,虚拟机的执行其实就是操作系统的线程在执行,并且会占用一定的系统资源,如CPU.内存.磁盘.网络等等.所以,如何高效的使用这些资源就是程序员在平时写代码时候的一个努力的方向.本文要说的线程池就是一种对 CPU 利用的优化手段. 线程池,百度百科是这么解释的: 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的

  • Java线程池使用与原理详解

    线程池是什么? 我们可以利用java很容易创建一个新线程,同时操作系统创建一个线程也是一笔不小的开销.所以基于线程的复用,就提出了线程池的概念,我们使用线程池创建出若干个线程,执行完一个任务后,该线程会存在一段时间(用户可以设定空闲线程的存活时间,后面会介绍),等到新任务来的时候就直接复用这个空闲线程,这样就省去了创建.销毁线程损耗.当然空闲线程也会是一种资源的浪费(所有才有空闲线程存活时间的限制),但总比频繁的创建销毁线程好太多. 下面是我的测试代码 /* * @TODO 线程池测试 */ @

  • 一篇文章带你了解如何正确使用java线程池

    目录 1.线程是不是越多越好? 2.如何正确使用多线程? 3.Java线程池的工作原理 4.掌握JUC线程池API 总结 1.线程是不是越多越好? 在学习多线程之前,读者可能会有疑问?如果单线程跑得太慢,那么是否就能多创建多个线程来跑任务?并发的情况,线程是不是创建越多越好?这是一个很经典的问题,画图表示一下创建很多线程的情况,然后进行情况分析. 创建线程和销毁线程都是需要时间的,如果创建时间+销毁时间>执行任务时间就很不划算 创建后的线程是需要内存去存放的,创建的线程对应一个Thread对象,

  • Java 线程池原理深入分析

    Java 线程池原理 Executor框架的两级调度模型 在HotSpot VM的模型中,Java线程被一对一映射为本地操作系统线程.JAVA线程启动时会创建一个本地操作系统线程,当JAVA线程终止时,对应的操作系统线程也被销毁回收,而操作系统会调度所有线程并将它们分配给可用的CPU. 在上层,JAVA程序会将应用分解为多个任务,然后使用应用级的调度器(Executor)将这些任务映射成固定数量的线程:在底层,操作系统内核将这些线程映射到硬件处理器上. Executor框架类图 在前面介绍的JA

  • 深入理解Java编程线程池的实现原理

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPoolExecutor类中的方法讲起,

  • JAVA线程池原理实例详解

    本文实例讲述了JAVA线程池原理.分享给大家供大家参考,具体如下: 线程池的优点 1.线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用. 2.可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃. 线程池的创建 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQu

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

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

随机推荐