前言
线程池是Java中的一个重要概念,从Android上来说,当我们跟服务端进行数据交互的时候我们都知道主线程不能进行联网操作以及耗时操作,主线程进行联网操作在3.0之后会报一个NewWorkOnMainTHreadException的异常,而在主线程进行耗时操作则会引起ANR(Application Not Responding),但是我们经常要跟服务端进行交互,下载和上传数据等,这也就是进行联网操作,在初学时我们都是通过new Thread来开启一个线程进行联网操作,但是跟服务端交互多了,如果还是使用new Thread()来开启子线程,在一个应用中我们频繁的去通过这个方法去开启线程,这对性能来说是很大的浪费,频繁的开启销毁线程对内存的消耗是很大的,而频繁的开启线程也让整个应用的线程管理显得很混乱,这是不可取的,这时候使用线程池就可以解决这些问题了,这篇文章我尝试将线程池概念和应用说清楚,然后封装一个自定义的线程池策略以后根据业务需求稍微更改下相关的参数即可。
线程池好处
线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。 - - - - -Doug Lea(线程池设计者)
从前文也可以得出线程池有下面几个好处
(1)自定义线程池策略可以重用线程池中的线程,避免应用中频繁的创建和销毁线程所造成的内存消耗以及性能上不必要的开销;
(2)通过控制线程池的最大线程数能有效控制线程池的最大并发数,避免大量的线程同一时间并发抢占系统资源而造成的阻塞和界面卡死;
(3)可以自己管理线程,定时执行,间隔循环执行,线程关闭等,通过对线程的管理可以避免滥用多线程造成的问题,比如内存泄露、界面卡死、CPU阻塞等
ThreadPoolExecutor构造方法解释
线程池有着很深的体系,如果每个类每个接口都细说显得不现实,所以这里重讲它的实现类,Executor是线程池的顶级接口,而ThreadPoolExecutor是它的实现类,这个类提供了一系列方法来配置我们自己的线程池,它也封装了一些典型的线程池策略供我们使用.
ThreadPoolExecutor继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口,ExecutorService接口继承了Executor,通过改变ThreadPoolExecutor构造方法里的参数我们可以自己定义线程池的属性,它一共有四个构造方法,比较常用的是下面这个方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
但是为了对这个类进行说明我选择的是参数最多的方法,因为不同构造方法最终回调的都是这个方法,只是设计者帮我做了一些默认的操作所以不用自己设置。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
线程池的保存的线程数,它包括空闲线程,上面是api中的解释,其实详细了说这是线程池中的核心线程数,在默认情况下,即使线程是空闲状态,这个核心线程也会在线程池中存在。但是设计进行了一些逻辑处理
/**
* Returns true if this pool allows core threads to time out and
* terminate if no tasks arrive within the keepAlive time, being
* replaced if needed when new tasks arrive. When true, the same
* keep-alive policy applying to non-core threads applies also to
* core threads. When false (the default), core threads are never
* terminated due to lack of incoming tasks.
*
* @return {@code true} if core threads are allowed to time out,
* else {@code false}
*
* @since 1.6
*/
public boolean allowsCoreThreadTimeOut() {
return allowCoreThreadTimeOut;
}
/**
* If false (default), core threads stay alive even when idle.
* If true, core threads use keepAliveTime to time out waiting
* for work.
*/
private volatile boolean allowCoreThreadTimeOut;
从注释我们可以看出如果把allowCoreThreadTimeOut的属性值设置为true(默认是false)那么现在空闲的核心线程在等待新任务时会使用一个超时策略,这个时间间隔由keepAliveTime设置,当空闲线程等待的时间超过设置时间时核心线程将被终止。
maximumPoolSize
线程池允许容纳的最大线程数,当活动的线程达到这个最大数时如果后续还有任务,那新任务将会被阻塞(通过参数可以设置阻塞时的不同处理策略)。
keepAliveTime
非核心线程闲置时的超时时长,非核心线程可以这么理解,比如核心线程数是2,最大线程数3,那么这三个里面有一个就是非核心线程了,用个比喻就是临时工,当核心成员干不完任务就会叫上临时工一起,但是任务完成了临时工什么时候辞退就是自己指定了(比喻可能不恰当,不带任何其他的隐喻),回到文章,当超过这个时长时,非核心线程就会被回收,但是刚才介绍corePoolSize的时候也说了,如果把allowCoreThreadTimeOut的属性设置为true,keepAliveTime也可以同样对核心线程进行终止操作。
unit
就是keepAliveTime的时间单位,直接用TimeUnit这个枚举就可以点出来,常用的有TimeUnit.MINUTS(分),TimeUnit.SECONDS(秒),TimeUnit.MILLISECONDS(毫秒).不常用的有TimeUnit.HOURS(小时),TimeUnit.DAYS(天)。
workQueue
线程池中的任务队列(阻塞队列),线程池的execute方法提交的Runnable对象存储在这个队列里面,BlockingQueue是线程安全的,常用的阻塞队列有如下五个
(1).LinkedBlockingQueue
链表阻塞队列,这个队列由一个链表构成,它内部维护了一个数据缓存队列,这个队列按 FIFO(先进先出)排序元素。队列的头部 是在队列中时间最长的元素。队列的尾部 是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。 当构造这个队列对象时,如果不指定队列容量大小,它的默认容量大小是Integer.MAX_VALUE(无限大),这样就是一个无界队列,除非插入节点会使队列超出容量,否则每次插入后会动态地创建链接节点。
(2).ArrayBlockingQueue
数组阻塞队列,这是一个有界阻塞队列内部,维护了一个定长数