- 为什么要使用线程池?
- 因为新线程的创建和销毁 对于CPU来说,存在性能开销的问题。使用线程池可以减少这一部分开销,为服务器提升性能。
- 对活动线程的管理。活动线程也会消耗系统资源,如果 JVM 中创建了太多的线程,可能会使系统的内存不足 或 “切换过度” 导致系统资源不足,使用线程池可以限制 请求的数量 等信息,提供了一种 “池化技术” 的来管理线程的能力。
- 线程池的使用场景
- 单个任务处理的时间比较短
- 需要处理的任务数量大
- [强制]线程池不允许使用
Executors去创建,而是通过ThreadPoolExecutor的方式,这样的的处理可以让程序员本身更加明确线程池的运行规则,规避资源耗尽的风险。
使用JDK本身自带的四种线程池来实现的弊端是什么呢?
newFixedThreadPool和newSingleThreadExecutor- 允许请求的队列长度为
Integer.MAX_VALUE,这样可能会堆积大量的请求,从而导致 OOM。
- 允许请求的队列长度为
newCachedThreadPool和newScheduledThreadPool- 允许的创建线程数量为
Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
- 允许的创建线程数量为
自定义线程池的实现及参数
ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, // 最少线程数(core:核心)
5, // 最大线程数
5, // 线程空闲时长
TimeUnit.SECONDS, // 线程空闲的时间单位
null, // 线程工作队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy()); // 拒绝执行线程任务时的策略
线程池在使用完毕之后,需要 shutdown() 进行平滑关闭(如果还有未执行完的任务,就等待它们执行完)。
-
添加任务到线程池 使用
execute(Runnable)方法将线程添加到线程池,Runnable 被称为任务,任务执行的方法就是 Runable 类型对象的run()方法。 -
任务线程的执行优先级
- 如果此时线程池中的数量小于 corePoolSize ,即使线程池中的线程都处于空闲状态,也要创建的新的线程来处理被添加的任务。
- 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
- 如果此时线程池中的数量大于 corePoolSize,缓冲队列 workQueue 已满,并且线程池中的数量小于 maxmumPoolSize,创建新的线程来处理被添加的任务。
- 如果此时线程池中的数量大于 corePoolSize,缓冲队列 workQueue 已满,并且线程池中的数量等于 maxmumPoolSize,那么通过hander所指定的策略来处理此任务。
corePoolSize > workQueue > maximumPoolSize
如果三者都满了,使用handler配置的策略处理被拒绝的任务。
workQueue常用的是:java.util.concurrent.ArrayBlockingQueue
handler有四种配置策略:
ThreadPoolExecutor.AbortPolicy()- 抛出java.util.concurrent.RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy()- 重试添加当前的任务,他会自动重复调用execute()方法
ThreadPoolExecutor.DiscardOldestPolicy()- 抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy()- 抛弃当前的任务