JUC包下有一个重要的线程池的实现,大大优化方便了我们对线程的使用,而不再是传统的new一个Thread。线程池相对传统的直接创建线程主要有三个优点:
1.统一管理线程,可以重用存在的线程,避免多次的创建、消亡的开销,使得性能表现得更好
2.可以有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源的竞争,避免阻塞
3.提供定时执行、定期执行、单线程、并发数控制等功能
通过Executors工厂方法来创建一个线程池。该工厂方法提供了主要四种重要类型的线程池(针对不同应用场景)
1.CacheThreadPool
这是一个线程数变动性非常强的线程池,默认配置下,它可以开启无限多个线程(Integer.maxSize 和 JVM允许线程数范围内)。且如果该线程池里的线程在60秒内如果是处于空闲状态(即没任务执行),那么该线程就会被回收,不再由线程池维护。如果有新任务进来时,由于之前的线程池里的线程已被回收,那么新的线程也会再次创建。当执行完任务,60秒内依旧无新任务的可执行话,那么该线程又会被再次回收。
综合该线程池的特性,我们可以思考下什么情况下应该使用这类线程池。比如:我们的应用服务器上面,会在非固定时间(时间跨域度会尽可能大)和非固定的任务数量。
2.FixedThreadPool
FixedThreadPool一个固定数量的线程池,且该线程池不会随着任务的变化而增多或减少线程数量。即该线程池下的线程池如果你不主动调用销毁shutdowm、purge之类的方法。那么这些线程将会永远被线程池维护着。
3.SingleThreadExecutor
SingleThreadExecutor是一个固定单线程的线程池,该线程池会永远都保持着一个线程的活动状态,如果该线程池的单线程因某些异常而退出后,线程池会继续创建一个新的线程。
4.ScheduledThreadPool(返回类型是ScheduledExecutorService)
ScheduledThreadPool是一个支持任务定时调度的线程池。
线程池主要有6大参数,参数的设置是否合理,将会直接影响到线程池的性能。
1.CorePoolSize(核心线程数):在创建了线程池后,默认情况下,线程池中是没有线程,当有新任务进来时,才会创建新的线程去执行这个任务。当再有另外一个任务进来时,即使刚才的线程已经处于空闲状态,线程池也是会创建新的线程去执行这个新任务。当线程池中的线程数达到CorePoolSize的时候,就会把新进来的任务放到任务队列(另一个重要参数)中。核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
2.MaxPoolSize(最大线程数):当线程池中的线程数大于等于CorePoolSize的时候,而且任务队列也已经满了,这个时候,线程池就会创建新的线程,直到线程数目达到MaxPoolSize。如果线程数目已经达到MaxPoolSize,任务队列又已经满了,那么线程池则会拒绝处理任务,并抛出运行时异常(这里涉及另一个参数任务拒绝处理器,有四种策略,默认abortPolicy--决绝执行,并抛出运行时异常)。
3.KeepAliveTime(运行保空闲时间):当线程空闲时间达到KeepAliveTime,该线程会退出,直到线程数目等于CorePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
4.QueueCapacity(任务队列容量):当核心线程数达到最大时,新任务会放在队列中排队等待执行.
还有就是 workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
PriorityBlockingQueue
5.AllowCoreThreadTimeout:是否允许核心线程空闲退出,默认值为false.
6.RejectedExecutionHandler(任务拒绝处理器):
* 两种情况会拒绝处理任务:
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
* 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
* ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 丢弃任务,抛运行时异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
* 实现RejectedExecutionHandler接口,可自定义处理器
1.当线程数小于核心线程数时,创建线程。
2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3.当线程数大于等于核心线程数,且任务队列已满。
1.若线程数小于最大线程数,创建线程。
2.若线程数等于最大线程数,抛出异常,拒绝任务。
参数的设置跟系统的负载有直接的关系,下面为系统负载的相关参数:
corePoolSize设置
每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即tasks*tasktime个线程数。假设系统每秒任务数为100~1000,每个任务耗时0.1秒,则需要100*0.1至1000*0.1,即10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下第秒任务数小于200,最多时为1000,则corePoolSize可设置为20。
queueCapacity设置
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即队列长度可设置为400。
队列长度设置过大,会导致任务响应时间过长,切忌以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。
maxPoolSize设置
当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60。
keepAliveTime设置
线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。
allowCoreThreadTimeout设置
默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。
RejectedExecutionHandler设置
设置默认是AbortPolicy,会抛出异常。