您的位置:首页 > 职场人生

线程池好处和核心参数等面试必备

2019-06-02 00:03 781 查看
版权声明:原创文章欢迎转载,转载请注明出处 https://blog.csdn.net/w605283073/article/details/90734059

一、背景

线程池的优点,以及核心参数是面试的重点。

在工作中也经常用到,大多人就看着之前源码怎么写copy一份就完事,没有深入得思考过为啥这样。

还有就是核心线程池参数理解可能不太透彻。

本文介绍使用线程池的优势以及相关核心知识点。

二、线程池的优势

现在不管是Java线程池,还是数据库连接池,redis缓存连接池,包括dubbo的线程池等等都是为了复用线程,避免频繁的创建和销毁线程浪费大量的系统资源,增加并发编程的风险。

线程池的作用:

  1. 控制线程,通过控制线程来控制最大并发数
  2. 实现任务线程队列缓存策略和拒绝机制。
  3. 实现某些与时间相关的功能,如定时任务,周期执行等。
  4. 隔离线程环境,一个线程专门执行耗时任务,另外一个线程执行响应要求高的任务。

 

三、线程池核心参数

线程池的核心参数是面试的一个重点!!!!

一般通过创建java.util.concurrent.ThreadPoolExecutor对象来创建线程池。

主要包括5个参数,如图所示:

其实源码的注释就是最好的学习材料,

 

第一个参数:corePoolSize: 核心常驻线程池。如果等于0,任务执行完,没有任何请求进入则销毁线程;如果大于0,即使本地任务执行完毕,核心线程池也不会被销毁。这个参数设置非常关键设置过大浪费资源,设置过小导致线程频繁创建或销毁。

第2个参数:maximumPoolSize:线程池同时执行的最大现场等是maximumPoolSize表示线程池能够容纳同时执行的最大线程数。

If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full.

如果线程池中的线程数大于核心线程数且队列满了,且线程数小于最大线程数,则会创建新的线程。

这一块参见源码:java.util.concurrent.ThreadPoolExecutor#execute

如果maximumPoolSize与corePoolSize相等,即是固定大小线程池。


第3个参数:keepAliveTime表示线程池中的线程空闲时间,当空闲时间达到keepAliveTime值时,线程会被销毁,直到只剩下corePoolSize个线程为止,避免浪费内存和句柄资源。

在默认情况下,当线程池的线程数大于corePoolSize时,keepAliveTime才会起作用。

但是当ThreadPoolExecutor的allowCoreThreadTimeOut变量设置为true时,核心线程超时后也会被回收。


第4个参数: TimeUnit表示时间单位。keepAliveTime 的时间单位通常是TimeUnit.SECONDS。


第5个参数: workQueue 表示缓存队列。当请求的线程数大于maximumPoolSize时,线程进入BlockingQueue阻塞队列。后续示例代码中使用的LinkedBlockingQueue是单向链表,使用锁来控制入队和出队的原子性,两个锁分别控制元素的添加和获取,
是一个生产消费模型队列。


第6个参数: threadFactory 表示线程工厂。它用来生产一组相同任务的线程。线程池的命名是通过给这个factory增加组名前缀来实现的。在虚拟机栈分析时,就可以知道线程任务是由哪个线程工厂产生的。


第7个参数: handler 表示执行拒绝策略的对象。当超过第5个参数workQueue的任务缓存区上限的时候,就可以通过该策略处理请求,这是一种简单的限流保护。

 

友好的拒绝策略可以是如下三种:

  1. 保存到数据库或者MQ进行削峰填谷,在空闲时取出来执行。
  2. 转向友好的提示页面
  3. 打印日志

 

四、Executors的5个核心方法

Executors.newWorkStealingPool: JDK8引入,创建持有足够线程的线程池支持给定的并行度,并通过使用多个队列减少竞争,此构造方法中把CPU数量设置为默认的并行度。

Executors.newCachedThreadPool: maximumPoolSize 最大可以至Integer.MAX_VALUE,是高度可伸缩的线程池,如果达到这个上限,相信没有任何服务器能够继续工作,肯定会拋出OOM异常。

keepAliveTime 默认为60秒,工作线程处于空闲状态,则回收工作线程。

如果任务数增加,再次创建出新线程处理任务。

这里设置保活时间是为了线程池中的线程尽可能得能够复用。

这点和redis的key过期时间有相通之处,redis设置过期时间
●Executors.newScheduledThreadPool: 线程数最大至Integer.MAX_ VALUE,与上述相同,存在OOM风险。

它是ScheduledExecutorService接口家族的实现类,支持定时及周期性任务执行。相比Timer, ScheduledExecutorService更安全,功能更强大,与newCachedThreadPool的区别是不回收工作线程。


●Executors.newSingleThreadExecutor: 创建一个单线程的线程池,相当于单线程串行执行所有任务,保证按任务的提交顺序依次执行。


●Executors.newFixedThreadPool:输入的参数即是固定线程数,既是核心线程数也是最大线程数,不存在空闲线程,所以keepAliveTime等于0:

[code]public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

这类使用了无界队列,如果瞬间请求过大有OOM风险。

[code]/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}

除了newWorkSteelingPool外,其他四个创建方式都存在资源耗尽的风险。

五、拒绝策略

参考《Java ThreadPoolExecutor的拒绝策略》

https://blog.csdn.net/w605283073/article/details/89930154

《Java ThreadPoolExecutor的拒绝策略CallerRunsPolicy的一个潜在的大坑》

https://blog.csdn.net/w605283073/article/details/89930497

六、学习技巧

关于线程池这里,比较好的学习技巧是

  1. 直接看JDK源码,很多地方注释写的很清楚
  2. 写DEMO观察效果
  3. 多参考相关的经典图书(参见第七部分)

 

七、参考资料

《码处高效Java开发手册》第七章 并发与多线程

《Java并发编程的艺术》第九章 Java中的线程池  第十章  Executor框架

 

 

如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。

 

另外欢迎加入我的知识星球,知识星球ID:15165241 一起交流学习。

https://t.zsxq.com/Z3bAiea  申请时标注来自CSDN。

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: