Java并发之线程池学习笔记

JUC包下有一个重要的线程池的实现,大大优化方便了我们对线程的使用,而不再是传统的new一个Thread。线程池相对传统的直接创建线程主要有三个优点:

1.统一管理线程,可以重用存在的线程,避免多次的创建、消亡的开销,使得性能表现得更好

2.可以有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源的竞争,避免阻塞

3.提供定时执行、定期执行、单线程、并发数控制等功能

如何使用线程池

通过Executors工厂方法来创建一个线程池。该工厂方法提供了主要四种重要类型的线程池(针对不同应用场景)

Java并发之线程池学习笔记_第1张图片

1.CacheThreadPool 


这是一个线程数变动性非常强的线程池,默认配置下,它可以开启无限多个线程(Integer.maxSize 和 JVM允许线程数范围内)。且如果该线程池里的线程在60秒内如果是处于空闲状态(即没任务执行),那么该线程就会被回收,不再由线程池维护。如果有新任务进来时,由于之前的线程池里的线程已被回收,那么新的线程也会再次创建。当执行完任务,60秒内依旧无新任务的可执行话,那么该线程又会被再次回收。 
综合该线程池的特性,我们可以思考下什么情况下应该使用这类线程池。比如:我们的应用服务器上面,会在非固定时间(时间跨域度会尽可能大)和非固定的任务数量。

2.FixedThreadPool


FixedThreadPool一个固定数量的线程池,且该线程池不会随着任务的变化而增多或减少线程数量。即该线程池下的线程池如果你不主动调用销毁shutdowm、purge之类的方法。那么这些线程将会永远被线程池维护着。

3.SingleThreadExecutor

Java并发之线程池学习笔记_第2张图片
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.若线程数等于最大线程数,抛出异常,拒绝任务。

线程池参数设置

参数的设置跟系统的负载有直接的关系,下面为系统负载的相关参数:

  • tasks,每秒需要处理的最大任务数量
  • tasktime,处理第个任务所需要的时间
  • responsetime,系统允许任务最大的响应时间,比如每个任务的响应时间不得超过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,会抛出异常。

你可能感兴趣的:(Java并发)