Executor与线程池
有没有思考过,为什么要用线程池?
想必你已经猜到,Java创建对象,仅在JVM堆中分配一块内存,创建线程,却需要调用操作系统API,操作系统再分配一系列资源,是一个非常耗时,好资源的过程。惯用思想,池化资源,得到共用。
想法没错,但是java的自带的线程池并不是采用池化资源来设计的,而是生产者-消费者模式。
Java 提供的线程池相关的工具类中,最核心的是ThreadPoolExecutor,下图是ThreadPollExecutor跟上游类之间的关系我已经列出
如何在IDEA中查看类图 参考:https://www.cnblogs.com/amberJava/p/12420875.html
其中红框是最完备的方法。具体含义,
- corePoolSize:表示线程池保有的最小线程数。有些项目很闲,但是也不能把人都撤了,至少要留 corePoolSize 个人坚守阵地。
- maximumPoolSize:表示线程池创建的最大线程数。当项目很忙时,就需要加人,但是也不能无限制地加,最多就加到 maximumPoolSize 个人。当项目闲下来时,就要撤人了,最多能撤到 corePoolSize 个人。
- keepAliveTime & unit:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?很简单,一个线程如果在一段时间内,都没有执行任务,说明很闲,keepAliveTime 和 unit 就是用来定义这个“一段时间”的参数。也就是说,如果一个线程空闲了
keepAliveTime & unit
这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了。 - workQueue:工作队列,和上面示例代码的工作队列同义。
- threadFactory:通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字。
- handler:通过这个参数你可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略,你可以通过 handler 这个参数来指定。ThreadPoolExecutor 已经提供了以下 4 种策略。
- CallerRunsPolicy:提交任务的线程自己去执行该任务。
- AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
- DiscardPolicy:直接丢弃任务,没有任何异常抛出。
- DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。
不过不建议使用 Executors 的最重要的原因是:Executors 提供的很多方法默认使用的都是无界的 LinkedBlockingQueue,高负载情境下,无界队列很容易导致 OOM,而 OOM 会导致所有请求都无法处理,这是致命问题。所以强烈建议使用有界队列
列出了默认的几个创建线程池的方法。
那么在使用线程池需要注意哪些方面呢?
1.使用有界队列时,当任务过多时,线程池会触发执行拒绝策略,线程池默认的拒绝策略会 throw RejectedExecutionException 这是个运行时异常,对于运行时异常编译器并不强制 catch 它,所以开发人员很容易忽略。所以默认策略慎用,建议根据实际情况,用自己的策略
2.考虑执行线程遇到的各种异常,最稳妥和简单的方案还是捕获所有异常并按需处理。
==========================================================================
如果您觉得这篇文章对你有帮助,可以【关注我】或者【点赞】,希望我们一起在架构的路上,并肩齐行
==========================================================================
==========================================================================