1.线程池优点
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
2. 源码分析
1)线程池的创建
在创建线程池时会传入7个参数,分别为:
corePoolSize:线程池核心线程数量
核心线程会一直存活,即使没有任务需要执行。当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。
设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程
maximumPoolSize:线程池最大线程数量
当线程数>=corePoolSize,且任务队列已满时,线程池会创建新线程来处理任务
当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
keepAliverTime:非核心线程存活时间
当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间,当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
如果allowCoreThreadTimeout=true,则会直到线程数量=0
unit:存活时间的单位
workQueue:存放任务的队列
用来存储等待执行的任务,决定了线程池的排队策略,可以选择以下几个阻塞队列:
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序【有界队列】
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue【无界队列】
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue【无界队列】
PriorityBlockingQueue:一个具有优先级的无界阻塞队列【无界队列】
threadFactory:创建新线程使用的工厂
handler:超出线程范围和队列容量的任务的处理程序(线程池的饱和策略)
RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5中Java线程池框架提供了以下4种策略:
AbortPolicy:直接抛出异常
CallerRunsPolicy:使用调用者所在线程来运行任务
DiscardPolicy:不处理,丢弃掉
DiscardOldestPolicy:抛弃下一个即将被执行的任务,然后尝试重新提交新的任务(注:此策略一般不和PriorityBlockingQueue一起使用,因为会导致优先级最高的任务被丢弃)
2)向线程池提交任务(线程池的增长策略)
可以使用两个方法向线程池提交任务,分别为execute()和submit()方法
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
execute()方法逻辑为:(就是线程池的原理)
1.如果线程池中的线程数量少于corePoolSize(核心线程数量),那么会直接开启一个新的核心线程来执行任务,即使此时有空闲线程存在.
2.如果线程池中线程数量大于等于corePoolSize(核心线程数量),那么任务会被插入到任务队列中排队等待被执行.此时并不添加新的线程.
3.如果在步骤2中由于任务队列已满导致无法将新任务进行排队,这个时候有两种情况:
线程数量未达到maximumPoolSize(线程池最大线程数) , 立刻启动一个非核心线程来执行任务.
线程数量已达到maximumPoolSize(线程池最大线程数) , 拒绝执行此任务.ThreadPoolExecutor会通过RejectedExecutionHandler,抛出RejectExecutionException异常。
3)关闭线程池
可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。
它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止
shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
3. 合理配置线程池
获取CPU个数:Runtime.getRuntime().availableProcessors()
CPU密集型:CPU负载高,IO读取效率很高
配置 (CPU核数+1 )个线程
IO密集型:CUP负载低,IO读取耗时高
配置 CPU核数*(1 + 平均等待时间/平均工作时间) 个线程
混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。