多线程复习之线程池和Executor框架

一.线程池
1.线程池优点
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
2. 源码分析
1)线程池的创建

在创建线程池时会传入多个参数,分别为:
corePoolSize:线程池核心线程数量
核心线程会一直存活,即使没有任务需要执行。当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
maximumPoolSize:线程池最大线程数量
当线程数>=corePoolSize,且任务队列已满时,线程池会创建新线程来处理任务。当线程数等于maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常。
keepAliverTime:非核心线程存活时间
当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间,当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize。如果allowCoreThreadTimeout=true,则会直到线程数量=0。
unit:存活时间的单位
workQueue:存放任务的队列
用来存储等待执行的任务,决定了线程池的排队策略,可以选择以下几个阻塞队列:
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列
handler,超出线程范围和队列容量的任务的处理程序。RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5中Java线程池框架提供了以下4种策略。
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardPolicy:不处理,丢弃掉。
DiscardOldestPolicy:把队列里待最久的那个任务扔了,然后再调用execute()试试看能行不。
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密集型任务应配置尽可能小的线程,如配置N(cpu)+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。
二.Executor框架
1. Executor框架的结构
Executor框架主要由3大部分组成:

任务:包括被执行任务需要实现的接口,Runnable接口或Callable接口(需要使用一个类实现他们)
任务的执行:包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口,Executor框架有两个关键类实现了ExecutorService接口。(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
异步计算的结果:包括接口Future和实现Future接口的FutureTask类。
注:Executor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开,ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
2.核心类
2.1 ThreadPoolExecutor
通常使用工厂类Executors来创建。Executors可以创建3种类型的ThreadPoolExecutor.
1)FixedThreadPool(可重用固定线程数的线程池:核心线程等于最大线程数,空闲线程立即终止)
FixedThreadPool的corePoolSize = maximumPoolSize = nThreads。keepAliveTime = 0L,意味着多余的空闲线程会被立即终止。使用无界队列LinkedBlockingQueue作为线程池的工作队列。
 
2)SingleThreadExecutor(单线程线程池:串行执行)
只使用单个线程,适用于需要保证顺序地执行各个任务。其corePoolSize = maximumPoolSize = 1使用无界队列LinkedBlockingQueue作为线程池的工作队列。
3)CachedThreadPool(缓存线程池:无核心线程,最大线程无界,超分死亡)
CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。工作线程的创建数量几乎没有限制, 这样可灵活的往线程池中添加线程。如果长时间没有往线程池中提交任务,空闲线程超过指定的时间(默认为1分钟),则线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。如果有线程已经完成任务并且处于空闲状态,将复用该线程。使用无容量的阻塞队列SynchronousQueue作为工作队列,把主线程提交的任务传递给空闲线程执行。每个插入操作必须等待另一个线程的对应移除操作,反之亦然。
2.2 ScheduledThreadPoolExecutor(用于定时任务)
使用工厂类Executors来创建。主要解决两个场景问题:指定延时后执行任务、周期性重复执行任务
1)ScheduledThreadPoolExecutor
包含若干个线程,适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。
2)SingleThreadScheduledExecutor
只包含一个线程的ScheduledThreadPoolExecutor。适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。
在定时任务线程池中存储任务的队列是具有优先性质的阻塞队列(DelayQueue)。在这个队列中离下次执行时间最近的任务位于队首
2.3 Future接口
Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。当我们把Runnable接口或Callable接口的实现类提交(submit)给ThreadPoolExecutor或ScheduledThreadPoolExecutor时,它们会向我们返回一个FutureTask对象。
FutureTask除了实现Future接口外,还实现了Runnable接口,可以把FutureTask交给Executor执行;也可以通过ExecutorService.submit(…)方法返回一个FutureTask,然后执行FutureTask.cancel(…)方法或FutureTask.get()方法。除此以外,还可以单独使用FutureTask。
2.4 Runnable接口和Callable接口
Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。它们之间的区别是Runnable不会返回结果,而Callable可以返回结果。除了可以自己创建实现Callable接口的对象外,还可以使用工厂类Executors来把一个Runnable包装成一个Callable。
本站所有文章均由网友分享,仅用于参考学习用,请勿直接转载,如有侵权,请联系网站客服删除相关文章。若由于商用引起版权纠纷,一切责任均由使用者承担
极客文库 » 多线程复习之线程池和Executor框架

Leave a Reply