-
maximumPoolSize:线程池最大线程数,表示在线程池中最多能创建多少个线程。 当线程数量达到corePoolSize,且workQueue队列塞满任务了之后,继续创建线程 ,当线程池中的线程数量到达这个数字时,新来的任务会执行拒绝策略。
-
keepAliveTime:表示线程没有任务执行时最多能保持多少时间会被回收,注意,这个参数控制的是超过corePoolSize之后的“临时线程”的存活时间。
-
unit:参数 keepAliveTime 的时间单位。
-
workQueue:工作队列,存放提交的等待任务,其中有队列大小的限制。
-
threadFactory:创建线程的工厂类,通常我们会自定义一个threadFactory设置线程的名称,这样我们就可以知道线程是由哪个工厂类创建的,可以快速定位排查问题。
-
handler:如果线程池已满,新的任务进来时的拒绝策略。
ThreadPoolExecutor 参数含义是最常见的一个问题,如果面试者对这些参数比较了解,至少说明面试者在多线程运用层面不会存在太大的问题,反之,如果面试官提示某个参数后面试者还是一脸懵的话,那么基础印象分就会大打折扣。
线程池线程创建的流程是怎样的
线程池线程创建的时机可以用下面这张图简单表示。
线程创建流程是这样的:
-
如果当前运行的线程少于corePoolSize(核心线程数),则创建新线程来执行任务(执行这一步骤需要获取全局锁)。
-
如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue(阻塞队列/任务队列)。
-
如果无法将任务加入BlockingQueue(队列已满),则在非corePool中创建新的线程来处理任务(执行这一步骤也需要获取全局锁)。
-
如果创建新线程将使得当前运行的线程超出maximumPoolSize限制,任务将被拒绝,并执行线程饱和策略,如:RejectedExecutionHandler.rejectedExecution()方法。
注意:初始化线程池时,线程数为0。
工作列队有哪几种实现
存放任务的工作队列有6种主要的实现,分别是 ArrayBlockingQueue、LinkedBlockingQueue、LinkedBlockingDeque、PriorityBlockingQueue、DelayQueue、SynchronousQueue。它们的区别如下:
-
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
-
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列,在未指明容量时, 容量默认为 Integer.MAX_VALUE 。
-
LinkedBlockingDeque:使用双向队列实现的双端阻塞队列,双端意味着可以像普通队列一样 FIFO(先进先出),可以以像栈一样 FILO(先进后出)
-
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较,跟时间没有任何关系,仅仅是按照优先级取任务。
-
DelayQueue:同 PriorityBlockingQueue,也是二叉堆实现的优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
-
SynchronousQueue:一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用put()方法的时候就会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
拒绝策略有哪几种
线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也塞不下新任务了。这时候我们就需要拒绝策略机制合理地处理新进来的任务。JDK 内置的四种拒绝策略如下:
-
AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
-
CallerRunsPolicy:由调用线程处理该任务。(例如io操作,线程消费速度没有NIO快,可能导致阻塞队列一直增加,此时可以使用这个模式)。
-
DiscardPolicy:丢弃任务,但是不抛出异常。(可以配合这种模式进行自定义的处理方式)。
-
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
线程池的分类
Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是 ExecutorService。Java中 Executors 工厂类可以为我们自动创建不同策略配置的线程池,供我们直接使用。
▐ newCachedThreadPool
coreSize 线程数0,最大线程数无限制,线程的允许空闲时间是60s,阻塞队列是 SynchronousQueue。适用于“短任务”情况。由于采用SynchronousQueue,每当提交一个任务,都会超过阻塞队列的长度,导致创建新线程处理,所以说: 每当提交一个任务,都会创建一个线程,可能造成OOM。 此外,线程空闲1分钟就会销毁,所以该线程池可能会频繁地创建和销毁线程。
▐ newFixedThreadPool
coreSize 和最大线程数都是用户输入的,阻塞队列用的 LinkedBlockingQueue,线程的允许空闲时间是0s。其核心特性就是线程数不会增加,不会减少,线程池也不会自己销毁。由于阻塞队列是无限大的,不会执行拒绝策略。所以可能会堆积无限的请求,导致OOM。
▐ newSingleThreadExecutor
相当于线程数为1的 newFixedThreadPool,缺点和 newFixedThreadPool 一样。有的小伙伴可能会问,那它和单个线程有什么区别?
| newSingleThreadExecutor | Thread |
| — | — |
|
任务执行完成后,不会自动销毁,可以复用
|
任务执行完成后,会自动销毁
|
|
可以将任务存储在阻塞队列中,逐个执行
|
无法存储任务,只能执行一个任务
|
▐ newScheduledThreadPool
支持定时及周期性任务执行,需要注意的是,如果任务执行过程中抛出了异常就会停止执行任务,而且也不会再周期地执行该任务了。所以如果想保持任务周期执行,需要 catch 一切可能的异常。
▐ newWorkStealingPool
采用的 ForkJoin 框架,可以将任务进行分割,同时线程之间会互相帮助。另外,阻塞队列采用的 LinkedBlockingDeque,可以进行任务窃取。由于实际使用不多,这里只作了解。
实际使用时并不推荐这样去直接创建使用,阿里Java开发规约里面也有相应约束:
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!**
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!