一、线程池的使用背景:
网络请求通常有两种形式:
第一种形式是请求不是很频繁,而且每次连接后会保持相当一段时间来读数据或者写数据,最后断开,如文件下载,网络流媒体等。
第二种形式是请求频繁,但是连接上以后读/写很少量的数据就断开连接。
考虑到服务的并发问题,如果每个请求来到以后服务都为它启动一个线程,那么这对服务的资源可能会造成很大的浪费,特别是第二种情况。因为通常情况下,创建线程是需要一定的耗时的,设这个时间为T1,而连接后读/写服务的时间为T2,当T1>>T2时,我们就应当考虑一种策略或者机制来控制,使得服务对于第二种请求方式也能在较低的功耗下完成。
通常,我们可以用线程池来解决这个问题,首先,在服务启动的时候,我们可以启动好几个线程,并用一个容器(如线程池)来管理这些线程。当请求到来时,可以从池中去一个线程出来,执行任务(通常是对请求的响应),当任务结束后,再将这个线程放入池中备用;如果请求到来而池中没有空闲的线程,该请求需要排队等候。最后,当服务关闭时销毁该池即可。
二、线程池的使用条件:
假设一个服务器完成一项任务所需时间为:T1 是创建线程时间,T2是线程执行任务的时间,T3是销毁线程的时间。 如果:T1 + T3 远大于 T2,则可以采用线程池的方法,以提高服务器性能。
由此可以看出,线程池的主要作用就是减少线程的创建和销毁时间。
三、线程池的组成:
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括:创建线程池、销毁线程池、添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
三、线程池的类型:
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
四、线程池的几个重要的类:
线程池有几个比较重要的几个类:
ExecutorService | 真正的线程池接口。 |
ScheduledExecutorService | 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。 |
ThreadPoolExecutor | ExecutorService的默认实现。 |
ScheduledThreadPoolExecutor | 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。 |
1. ThreadPoolExecutor详解:
ThreadPoolExecutor是Executors类的底层实现。
ThreadPoolExecutor(
int corePoolSize, // 池中所保存的线程数,包括空闲线程
int maximumPoolSize, // 池中允许的最大线程数
long keepAliveTime, // 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
TimeUnit unit, keepAliveTime // 参数的时间单位
BlockingQueue<Runnable> workQueue, // 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务
ThreadFactory threadFactory, // 执行程序创建新线程时使用的工厂
RejectedExecutionHandler handler) // 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序