【前言】我们从事Android开发以来,都自始自终被灌输着处理耗时的任务时要在非UI线程做。于是我们有了各种处理并发的编程手段,无论是自己用new Thread(Runnable)新起工作线程(Worker thread),还是利用Android提供的API(AsnyTask,CursorLaoder等)都是处理耗时任务的解决方案。但是在一个大型的应用程序中,如果我们需要处理数量很多且频繁的耗时任务时,如果还是采用之前的手段,无疑会带来很多不便;一来频繁创建销毁线程会造成资源(内存和Cpu)的浪费,二来代码会显得很凌乱。于是我们提出了使用线程池来处理频繁的耗时任务。
在JDK1.5的类库中,JAVA的开发者就给我们提供了现成的线程池,java.util.concurrent包下就提供了线程池的api。
我的应用架构设计中,在处理并发的任务时,就要用到线程池,关于线程池,我把它想成是一个统一管理多线程任务的地方,具体来说,就是线程池接受多线程任务(实现Runnable接口),并通过创建线程池时配置好的一些参数来统一管理和执行这些任务,我们在需要执行耗时任务的时候,只需要向线程池发送一个消息(希望执行的耗时操作)就可以了,当然,我们在发送请求的同时,可以传入一些回调的接口,这样就能在耗时任务执行完毕之后得到回调来更新UI。
如何实现自定义的线程池?
JDK提供的线程池固然可以满足一般的开发需求,但是实现自定义的线程池还是很有必要的。
自定义线程池一般可以继承自类ThreadPoolExcutor,该类的构造方法里面有几个很重要的参数,这几个参数决定着线程池的策略和处理能力。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize - 池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
这些参数既可以在构造的时候传入,也可以在后期通过set方法来设定。
setMaximumPoolSize(int maximumPoolSize)
setRejectedExecutionHandler(RejectedExecutionHandler handler)
setKeepAliveTime(long time, TimeUnit unit)
setCorePoolSize(int corePoolSize)
线程池表达了一种边界的概念,这个边界是通过corePoolSize和maximumPoolSize来体现的,如果线程池接受的任务小于corePoolSize,则新提交的任务会被线程池通过新建线程来处理,如果大于corePoolSize而小于maximumPoolSize,则新传入的任务会被排队队列容纳,如果队伍容纳不下或者队列是直接提交型队列时,任务就会被提交给线程池,线程池会创建新线程来处理任务,如果大于maximumPoolSize,则新加入的任务会被拒绝,这里要注意的时,被拒绝的意思并不等于被抛弃,只是会触发线程池的RejectExecutionHandler,这里我们可以通过设置自定义的handler来实现线程池的拒绝策略(比如用一个队列来保存被拒绝的任务,以后还可以从该队列取出任务继续执行)。
在自定义线程池的时候,选择合适的排队队列显得尤为重要,我们一般有以下三种队列可选
SynchronousQueue 直接提交队列,该队列不会保存任务,而是会把任务直接提交给线程池。
LinkedBlockingQueue 无界队列,这队列没有极限,可以无限加入任务保存,这样maximumPoolSize就不起作用了,创建的线程数就永远不会超过corePoolSize了。
ArrayBlockingQueue 有界队列。
如何处理被拒绝任务?
1.定义被拒绝的策略
当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝。在以上两种情况下,execute 方法都将调用其RejectedExecutionHandler 的RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。
ThreadPoolExecutor.AbortPolicy
用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException.
ThreadPoolExecutor.DiscardOldestPolicy
用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。
ThreadPoolExecutor.DiscardPolicy
用于被拒绝任务的处理程序,默认情况下它将放弃被拒绝的任务。
2.自定义RejectedExecutionHandler
使用自定义RejectedExecutionHandler来定义线程池的拒绝策略,比如用一个队列来保存被拒绝的任务以便以后继续执行或者直接丢弃。
更多灵活的处理
ThreadPoolExcutor提供了一些可被覆写的hook方法
afterExecute(Runnable r,Throwable t)
基于完成执行给定 Runnable 所调用的方法。
beforeExecute(Thread t,Runnable r)
在执行给定线程中的给定 Runnable 之前调用的方法。
这两个方法在每个任务被线程池执行前后都被会回调,所以我们可以覆写这两个方法来自定义需要的功能。