Android开发之对线程池的理解

  • 今天撸代码时遇到这个点,于是回头看了一遍,重新梳理了一下Android线程池的使用,为了加深理解,顺便做了一个小Demo,谈技术要联系实际需求,而且不提源码的都是耍流氓,下面从Why、What、How三个大方面谈谈我对android线程池的部分理解,水平有限,错误之处还请指出。

Why | 为什么要用Android线程池

  • 首先要从实际需求中说起:再我们在移动端(主要指手机)中执行多项任务时,因为考虑到手机cpu和内存性能有限(Android来说,就算你是821+8G+128G的小米5S写一个循环new线程小程序去运行内存也是分分钟消耗完,主要的日常程序加手机rom也接近3G了吧),这时也不能去停止线程,尤其在线程中执行一些例如联网、下载、读取数据等耗时操作时,多条线程同时运行会降低程序运行效率,造成卡顿的效果,严重影响了用户体验,因此我们在开发中需要用线程池去管理这些线程。
  • 使用线程池的好处:可以避免频繁创建线程,合理利用系统资源,并且可以利用API提供的方法在线程执行中进行一些有效控制,如获取当前线程池的线程数,设定定时任务,shutdown某个进程等;

What | 什么是Android线程池

An {@link ExecutorService} that executes each submitted task using one of possibly several pooled threads, normally configured using {@link Executors} factory methods.

Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources,including Each {@code ThreadPoolExecutor} also maintains some basic statistics, such as the number of completed tasks.

—————— 我知道你不会认真看英文的分界线 So.————————

一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。

线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。

  • 以上是jdk1.5中对线程池的介绍,Android中对线程池的使用源于Java,主要是通过与线程相关的Executor接口,下面通过ThreadPoolExecutor这个Executor的实现类来对线程池进行了解。

  • ThreadPoolExecutor一共有四个构造方法,我们选参数最多的一个来分析一下:

    public ThreadPoolExecutor(
       int corePoolSize,           //核心线程数  
       int maximumPoolSize,        //最大线程数 等于核心线程+临时线程
       long keepAliveTime,         //临时线程等待新任务的时长
       TimeUnit unit,              //上一参数时间单位
       BlockingQueue<Runnable> workQueue,   //队列缓冲区的大小
       ThreadFactory threadFactory,      //线程处理工厂,一般用默认
       handler                         //超出线程范围和队列容量的处理策略
        ){}
    
  • 关于线程池的执行原理,我们先讲一个故事:

    比如LOL中你处于ADC位置,当你带着辅助在下路,对面打野中单上单全部来对付你们,于是对方五个人都在你和辅助前面,此时的你和辅助就是核心线程数,对面五人就是你的任务量。

    你一想不妙,二打五分分钟要被越塔强杀,于是你边喊队友边说人多欺负人少算什么,NB就一对一单挑,对面说哎哟你个青铜渣渣这么有勇气,那就来,于是对方三个人在一边等着,这时候等待的3个人就放进你设定的等待队列的任务,如果你设定等待队列数大于等于3,那么你和辅助还是按照一对一的原则打完剩下的人;

    假如你设定等待队列数小于3,那剩下的人将由你过来的队友一对一解决,帮助你解决任务的队友,我们可以看作是临时线程。要是你和辅助加上支援的队友数量加上设定的等待队列少于对方人数5,那程序默认抛出异常,具体地说就是等于送人头了。

    对了,在线程池中是不存在“我能反杀”这种错觉和doublekill情况的。

  • 关于线程池的处理流程:直接上图:

  • 线程池处理流程

  • 图中所示是线程池处理流程(请无视灵魂画手的画风),

理解图中步骤:

  1. 第一步:任务进来时由核心线程去执行,此时核心线程数为5,任务数量小于5不再向下,超过5时继续向下执行;
  2. 第二步:多余的任务会添加到队列缓冲区中排队等候,此时假设缓冲区大小为10,那么当进来队列中的任务数小于10时将排队等待核心线程去循环执行完,当进来队列中的任务数大于10继续向下执行;
  3. 第三步:剩余的任务将开启临时线程去执行,代码中是根据maximumPoolSize - corePoolSize的值决定,图中假设的临时线程数量为3,那么可知最大线程数为8,当剩余的任务数小于临时线程数时,剩余任务将由临时线程执行完,否则将向下执行;
  4. 第四步:最终未得到执行的,被发好人卡的任务,将由代码中设定的方法处理(与handler相关),源码中提供了四种处理方法:

    AbortPolicy         //默认处理方法,直接抛出RejectedExecutionException异常
    CallerRunsPolicy    //调用运行该任务的execute本身来执行,减缓新任务提交
    DiscardPolicy       //不想理你,直接删除
    DiscardOldestPolicy //按先进先出顺序踢出较老的线程
    
  • 还需补充的是,其中关于等待队列,API中提供三种通用策略,因为这一块还不是很熟,就不多做介绍,可参考API文档和大神们的博客,这里贴上API文档,三种策略分别为:

1、直接提交。工作队列的默认选项是SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
2、无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize
线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
3、有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。


How | 怎么使用Android线程池

  • 下面我写一个Demo来说明一下:

    package org.doubi;
    
    import java.util.concurrent.Executors;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.RejectedExecutionHandler;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author piao
     *  演示线程池原理
     */
    public class Demo1 {
        //定义参数
        private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
        // 测试线程池
        public static void main(String[] args) {
    
            //int corePoolSize      核心线程数
            // int maximumPoolSize  最大线程数 等于图中的核心线程数 + 临时工线程数 
            // long keepAliveTime   临时线程的存活时间 
            // TimeUnit unit        时间单位 = 秒(可知上一参数单位为秒)
            // BlockingQueue<Runnable> workQueue    队列缓冲区的大小 
            // ThreadFactory threadFactory          线程处理工厂,一般用默认
            // handler                              任务处理策略
            ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 8, 1,
                    TimeUnit.SECONDS, new LinkedBlockingDeque(3),
                    Executors.defaultThreadFactory(),defaultHandler);
            for (int i = 0; i < 6; i++) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName());
                        try {
                            //方便查看测试结果
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }
    
  • 此时共6个任务,大于核心线程数小于最大线程数,由核心线程去执行,队列中有一个再等待执行,执行结果为:

这里写图片描述

  • 更改一下参数:设定 i < 10 ,此时共10个任务,核心线程为5,等待队列为3,因此需要启动两个临时线程去执行,结果如下:
    这里写图片描述

  • 再次更改参数:设定 i < 12,此时共10个任务,核心线程为5,等待队列为3,临时线程最大为3,因此有一个线程将被发好人卡,拒绝执行,安装配置的处理方法抛出异常,结果如下:

这里写图片描述

  • 至此,线程池的原理和基本执行流程就说完了,下面介绍一下Android中常用的四种线程池:

四种常用线程池

  • 1、固定大小线程池:FixThreadPool

     public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    
  • 2、单个后台进程线程池:SingleThreadPool

    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    
  • 3、无界线程池,可以进行自动线程回收的线程池:CachedThreadPool

      public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    
  • 4、执行定时任务以及有固定周期的重复任务:ScheduledThreadPool

     public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
            return new ScheduledThreadPoolExecutor(corePoolSize);
        }
    

    关于对队列的通用策略和四种常用线程池的理解,可以参考:
    http://blog.csdn.net/u012702547/article/details/52259529

  • 以上便是我对线程池的理解,经验有限,不足地方还请指教。另外具体到Android中的使用,还需要去了解AsyncTask类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值