前言
在Android开发中线程的使用肯定是少不了的,因为在主线程是不能做耗时操作的;但是使用线程也不能随意的直接通过new Thrad方式去使用,因为像列表这种页面,有很多的图片需要加载,只能异步执行,如果直接new,那对手机内存的压力可想而知;好在Android给我们提供了线程池这样一个东西用来管理线程的操作;话说Android给我们提供了AsyncTask这样一个类,它内部维护了一个线程池,但是很蛋疼的是这个线程池是串行执行的,做不到并发操作,既然这样我们只有自己来封装下了
本文所含代码随时更新,可从这里下载最新代码
传送门
ThreadPoolExecutor
Android中关于线程的API其实Java的,老祖宗是Executor这样一个接口,提供了一个执行线程的方法execute(Runnable command);还有另外一个接口ExecutorService继承自Executor,扩展了它的能力
不能总是接口啊,总要有一个人来实现这些盖世武功吧,没错,就是ThreadPoolExecutor这孙子了(其实它还有一个兄弟叫ForkJoinPool,也实现了这些功能),这就是今天的主角线程池的代表
Executors
大家伙应该知道Java还提供了一个Executors类,可以通过它来构建多种不同的线程池
- ExecutorService newCachedThreadPool():创建一个可缓存的线程池;其中核心线程数量为0,最大线程数量是Integer.MAX_VALUE(2的31次方减1),线程队列是SynchronousQueue,超时时间为60s,当线程空闲时间达到60s将被回收;每当提交线程的时候,如果线程池中有空闲线程,那就由它执行新任务,如果没有就新创建线程执行任务
- ExecutorService newFixedThreadPool(int nThreads):创建一个指定容量的线程池;其中核心线程数量和最大线程数量相同,线程队列是LinkedBlockingQueue,队列容量是Integer.MAX_VALUE,超时时间为0s,说明即使线程是空闲的,也不会被回收,这样线程池就能更快的响应请求;其次如果核心线程都在执行任务,其余任务只能等待,直到有核心线程空闲
- ExecutorService newSingleThreadExecutor():创建一个只有一个线程的线程池;其中核心线程数量和最大线程数量都是1,线程队列是LinkedBlockingQueue,队列容量是Integer.MAX_VALUE,超时时间为0;这种线程永远只有一个线程在工作
- ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建一个具有定时定期执行任务功能的线程池;核心线程数由我们指定,最大线程数是Integer.MAX_VALUE,超时时间10ms,线程队列DelayedWorkQueue;可以看到它返回一个接口类ScheduledExecutorService,实现类是ScheduledThreadPoolExecutor(继承ThreadPoolExecutor),它提供我们延迟启动任务和定时执行任务的方法
别看这些花里胡哨的方法,其实它们内部都是通过如下这种类似模式来创建线程池
new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
这下明白了吧,Executors类只是官方为了开发者方便对线程池进行了下封装,所以要想做一个适合自己APP的线程池,那就自己动手封装一个线程池吧(其实延迟定时执行任务我们可以通过ThreadPoolExecutor+Handler做到)
构造方法
ThreadPoolExecutor提供了四个重载的构造方法
- ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue< Runnable> workQueue)
- ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue< Runnable> workQueue,RejectedExecutionHandler handler)
- ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue< Runnable> workQueue,ThreadFactory threadFactory)
- ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue< Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
这里以参数最多的构造方法为例对里面的参数进行讲解
- corePoolSize:线程池中核心线程的数量;为了内存优化,在线程池维护了几个重要的线程,不达到一定条件不开辟其余线程
- maximumPoolSize :线程池中最大线程数量:这个数量是包括核心线程的,当线程池中的正在执行的线程池达到了这个数字,再提交线程如果你不做特殊处理将会抛出异常
- keepAliveTime:非核心线程的超时时长;当线程池中的非核心线程闲置时间超过这个值代表的时间后,将会被回收;同时如果调用ThreadPoolExecutor.allowCoreThreadTimeOut(true),那么核心线程也会符合这个设置
- unit:keepAliveTime值的单位,可以是时分秒等
- workQueue:存放待执行的线程;你通过execute方法提交线程,但是这些线程还没达到执行条件,那么就会保存在这个队列里
- threadFactory:创建线程池的工厂;在这个工厂里,我们可以指定线程的一些信息
- handler:线程提交拒绝策略;通常是线程池中的正在执行的线程数量已经达到了最大线程数,如果不传,默认是抛出一个RejectedExecutionException,所以最好传下
线程池运行逻辑
了解了构造方法后,那么一个线程池的通用运行逻辑是什么样的呢?也就是线程池里的线程执行逻辑是啥?
当我们通过execute方法提交一个线程后,它的去向如下几种(这里排除核心线程数为0的情况)
- 当线程池中正在执行的线程数量没有超过核心线程数量时,会立马新建一个核心线程执行任务
- 当线程池中正在执行的线程数量达到了核心线程数量,但是线程队列workQueue没有满,那提交的任务会保存在队列中
- 当线程池中正在执行的线程数量已经达到了核心线程数量,但线程队列workQueue已满,那就新建一个非核心线程执行任务
- 当线程池中正在执行的线程数量已经达到了最大线程数量,且线程队列workQueue已满,再提交任务将会拒绝执行或者抛出异常
自定义线程池
了解了上面这些知识点后,我们就可以来做一个符合我们自己APP需求的线程池了
首先要做的是确定线程池的核心线程的数量和最大线程数量
- 核心线程数量:其实通过上面的线程池运行逻辑可以知道,当线程池中正在执行的线程数量达到了核心线程数量,但是workQueue队列在从0添加到满且核心线程没有执行完的这个时间内,你提交任务是得不到执行的,因为都保存在队列了,这其实是很要命的,想要的并发效果实际上是没有达到;但是又不能将核心线程数量设置为0,这样就必须要等队列满了才开始开辟非核心线程执行任务,如果你的队列长度非常大,等待你的将是OOM;所以这个队列的长度一定要设置好
- 最大线程数量:这个值就需要你考虑下APP里有多大的并发量,比如一个屏幕能显示多少个列表
还有一个就是要传入一个handler,应对线程池拒绝的情况,这样我们就可以定义一个线程池了,如下
/**
* @Description TODO(全局使用的线程池)
* @author cxy
* @Date 2018/11/14 17:22
*/
public class LocalThreadPools {
private static String TAG = LocalThreadPools.class.getSimpleName();
private static ExecutorService THREAD_POOL_EXECUTOR;
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT-1,4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2;
private static final int KEEP_ALIVE_SECONDS = 60;
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(8);
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "MangoTask #" + mCount.getAndIncrement());
}
};
private void initThreadPool() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory,new RejectedHandler()){
@Override
public void execute(Runnable command) {
super.execute(command);
Log.e(TAG,"ActiveCount="+getActiveCount());
Log.e(TAG,"PoolSize="+getPoolSize());
Log.e(TAG,"Queue="+getQueue().size());
}
};
//允许核心线程空闲超时时被回收
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
private class RejectedHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//可在这里做一些提示用户的操作
Toast.makeText(mContext.get(),"当前执行的任务过多,请稍后再试",Toast.LENGTH_SHORT).show();
}
}
private WeakReference<Context> mContext;
private static LocalThreadPools instance;
private LocalThreadPools(Context context){
mContext = new WeakReference<>(context);
initThreadPool();
}
public static LocalThreadPools getInstance(Context context){
if (instance == null) {
instance = new LocalThreadPools(context);
}
return instance;
}
public void execute(Runnable command){
THREAD_POOL_EXECUTOR.execute(command);
}
}
调用方法如下
LocalThreadPools.getInstance(this).execute(new Runnable() {
@Override
public void run() {
Log.e(TAG,"NAME="+Thread.currentThread().getName());
}
});
线程池关闭
既然有创建线程池的方法,那肯定有关闭咯,如下
/**
* 通过interrupt方法尝试停止正在执行的任务,但是不保证真的终止正在执行的任务
* 停止队列中处于等待的任务的执行
* 不再接收新的任务
* @return 等待执行的任务列表
*/
public List<Runnable> shutdownNow(){
return THREAD_POOL_EXECUTOR.shutdownNow();
}
/**
* 停止队列中处于等待的任务
* 不再接收新的任务
* 已经执行的任务会继续执行
* 如果任务已经执行完了没有必要再调用这个方法
*/
public void shutDown(){
THREAD_POOL_EXECUTOR.shutdown();
sPoolWorkQueue.clear();
}