线程池简介
线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如Tomcat。
线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。
- 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
线程池的使用
创建线程池
利用ThreadPoolExecutor提供的构造方法创建线程池
线程池的核心参数
- ArrayBlockingQueue(有界队列):队列长度有限,当队列满了就需要创建非核心线程执行任务,如果最大线程数已满,则执行拒绝策略
- LinkedBlockingQueue(无界队列):队列长度无限,当任务处理速度跟不上任务创建速度,可能会导致内存占用过多或OOM
- SynchronousQueue(同步队列):队列不作为任务的缓冲处理,队列长度为0
- 创建线程的工厂接口,默认使用Executors.defaultThreadFactory()
- 另外可以实现ThreadFactory接口,自定义线程工厂
- AbortPolicy:默认拒绝策略,中断抛出RejectedExecutionException异常
- CallerRunsPolicy:让提交任务的主线程来执行任务
- DiscardOldestPolicy:丢弃在队列中存在时间最久的任务,重复执行
- DiscardPolicy:丢弃任务,不进行任何通知
- 另外可以实现RejectedExecutionHandler接口,自定义拒绝策略
线程池的参数设计分析
核心线程数(corePoolSize)
核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定。
例如:一个线程执行一个任务需要0.1秒,1秒就执行10个任务;系统80%的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程。
此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照二八原则设计即可,即按照80%的情况设计核心线程数,剩下的20%可以利用最大线程数处理;
任务队列长度(workQueue)
任务队列长度,也就是设计 阻塞队列 能缓存多少个任务。任务队列长度一般设计为:核心线程数 / 单个任务执行时间 *2 即可;
例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200 ;
最大线程数(maximumPoolSize)
最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定;
例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么:
最大线程数 = ( 最大任务数 - 任务队列长度 ) * 单个任务执行时间;
即: 最大线程数 = (1000 - 200 ) * 0.1 = 80个;
最大空闲时间(keepAliveTime)
这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可。