java线程池原理 面试,线程和线程池面试题

  java线程池原理 面试,线程和线程池面试题

  00-1010简介1。说说你对线程池的理解?2.ThreadPoolExecutor、Executor、ExecutorService、Runnable、Callable和FutureTask是什么关系?3.谈谈队列在线程池中的作用?4.随着请求越来越多,谈谈线程池构造函数参数的意义和性能?5.coreSize和maxSize可以动态设置吗?有什么规则和限制吗?6.谈谈对线程空闲回收的理解。在源代码中是如何体现的?7.如果我想在线程池任务执行前后做一些资源清理,可以吗?我该怎么做?8.线程池中的线程创建。可以自定义拒绝请求的实现吗?怎么定制?9.说说你对工人的理解?10.说说执行submit方法的过程?1.说说线程执行完任务后在做什么?12.keepAliveTime设置为负数或0,表示无限阻塞?13.告诉我们Future.get方法是如何得到线程的执行结果的。14.摘要

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

  00-1010答:答题思路从大到小,从全面到局部。一般来说,可以说线程池结合了锁、线程、队列等元素。并且可以在请求量较大的环境下多线程处理请求,充分利用系统资源,提高处理请求的速度。具体可以从以下几个方面来阐述:

  ThreadPoolExecutor类结构;ThreadPoolExecutor coreSize、maxSize等重要属性;工人的重要作用;提交的整个过程。通过上面总分的描述,应该可以清楚的说明对线程池的理解。如果是面对面的面试,可以边聊边画线程池的整体架构图(见《ThreadPoolExecutor 源码解析》)。

  00-1010 A:以上六类可以分为两类:一类是定义任务类,一类是执行任务类。

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

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

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

  00-1010答:效果如下:

  当请求数大于coreSize时,可以将任务在队列中排队,让线程池中的线程慢慢消耗请求。在实际工作中,实际的线程数不可能等于请求数,队列提供了一种对任务进行排队的机制,起到了缓冲的作用。

  当一个线程消耗完所有线程时,它将阻塞队列中的数据。通过队列阻塞的功能,线程不会死。一旦队列中有数据,就可以立即使用。

  00-1010 A:线程池生成器的参数含义如下:

  CoreSize核心线程;

  MaxSize最大线程数;

  keepAliveTime线程空闲的最长时间;

  有很多队列可以选择,比如:1:同步队列。为了避免任务被拒绝,要求线程池的最大大小是无界的。缺点是当任务提交的速度超过消耗的速度时,可能会出现无限的线程增长;2: LinkedBlockingQueue,一个无界队列,未消耗的任务可以在这里等待;3: ArrayBlockingQueue,有界队列,可以防止资源耗尽;

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

  当执行器关闭或者最大线程和最大队列饱和时,可以使用RejectedExecutionHandler类来捕获异常。有四种处理策略:ThreadPoolExecutor。AbortPolicy,threadpooleexecutor . discard policy

  、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 小于原来的值,尝试干掉一些 workerpublic 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线程池面试题的资料请关注盛行IT其它相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: