Android开发-通过ExecutorService构建一个APP使用的全局线程池

版权声明:本文为博主原创文章,转载请标明出处!如果文章有错误之处请留言 https://blog.csdn.net/qq_30993595/article/details/84324681

前言

在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到满且核心线程没有执行完的这个时间内,你提交任务是得不到执行的,这其实是很要命的,想要的并发效果实际上是没有达到
  • 最大线程数量:这个值就需要你考虑下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<>(16);
    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(
                MAXIMUM_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();
    }

猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/84324681