安卓中的线程和线程池

本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。

Android沿用了Java的线程模式,其中线程也分主线程和子线程。

一、线程


1、主线程和子线程

一个程序的运行,就是一个进程的在执行,一个进程里面可以拥有很多个线程。

  • 主线程:也叫UI线程,用于运行四大组件和处理他们用户的交互。(主线程就是指进程所拥有的线程,在Java中默认一个进程只有一个线程,那么默认的线程就是我们的主线程)

  • 子线程: 用于执行耗时操作,比如 I/O操作和网络请求等。(安卓3.0以后要求耗访问网络必须在子线程种执行)更新UI的工作必须交给主线程,子线程在安卓里是不允许更新UI的。


2、安卓里,谁是线程

在安卓的世界里面,扮演线程的觉有有如下四个:

  • 1,Thread:线程就是它。它本来就是线程,最根本的就是它
  • 2,AsyncTask:封装了线程池和Handler
  • 3、HandlerThread:是同一个具有消息循环的线程,在他的内部可以使用Handler
  • 4,IntentServer : 内部使用HandlerThread来执行任务,任务完成后自动退出,因为是服务,所以优点是优先级高,系统尽量保证它存活。


二、线程池

1、线程的产生和销毁都会消耗性能

在操作系统中,线程是可以调度的最小单元,线程不能无限制产生,因为创建线程和销毁线程都会有相应的性能开销。

当系统中存在大量的线程的时候,系统会通过时间片轮转的方式调度每个线程,因此在大量线程的情况下没办法让每一个线程都得到绝对的并行,除非线程数量小于或者等于CPU的核心数,一般来说这是不可能的。
所以不可以在一个进程里面大量创建和销毁线程,这是非常消耗性能的行为,所以我们需要使用管理线程,这个管理的工具就叫做—— 线程池

    线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
    比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。


2、使用线程池的好处

  • 1,重用线程,避免重复地创建和销毁进程带来的没必要的性能开销
  • 2,管理控制最大并发数,避免大量的进程不受控制得因抢占系统资源而导致阻塞,也就是界面卡顿。
  • 3,提供定时执行和指定间隔循环等功能


4、Executor 和 ThreadPoolExecutor

安卓中的线程池的概念来源于 Java 中的 Executor。
Executor是一个接口,真正的线程池的实现是 ThreadPoolExecutor 。
ThreadPoolExecutor提供了一系列的参数来配置线程池,通常来说,安卓的线程主要有4种,后面会陆续说到,但是因为Android中的线程池都是直接或者间接通过配置ThreadPoolExecutor来实现的,所以我们这里先了解ThreadPoolExecutor。


5、ThreadPoolExecutor 的构造方法的参数

ThreadPoolExecutor是java.util.concurrent下的一个类,ThreadPoolExecutor有好几个构造方法,我们主要明白了他的构造的方法含义,就大概明白ThreadPoolExecutor是怎么用的了。

  • 常用的构造的方法
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }


  • 最长的构造方法
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

因为最长,所以我们分析这个最长的


  • corePoolSize : 线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态。

如果将executor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略(超时判断),如果在keepAliveTime指定的时间之外还处于等待状态的话,核心线程也会被终止(所以核心线程也不是可以毫无条件地牛逼下去的)


  • maximumPoolSize 线程池所能容纳的最大线程数,当活动线程达到这个数值之后,后续的新任务将会被阻塞在外面。

  • keepAliveTime 非核心线程闲置的超时时长,如果超过了这个时长,非核心线程就会被回收。
    当 ThreadPoolExecutor 的allowCoreThreadTimeOut属性被设置为true时,keepAliveTime也会作用于核心线程

  • unit 用于指定 keepAliveTime的参数的时间单位,这是一个枚举,常用的有 TimeUnit.MILLISECONDS (毫秒),TimeUnit.SECONDS (秒),TimeUnit.MINUTES (分钟)等

  • workQueue 线程池中的任务队列,通过线程池的 execute 方法提交到 Runnable 对象会存储在这个参数中。

  • threadFactort 线程工厂,为线程池提供创建新线程的功能,ThreadFactory是一个接口,它只有一个方法,Thread newThread(Runnable r)

  • RejectedExecutionHandler(不常用) 当线程池无法执行新任务时,(任务队列已满或者其他原因),那么 rejectedExecutio会抛出一个异常给调用者。


6、线程池的运行机制

一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。

当一个任务通过execute(Runnable)方法欲添加到线程池时:

  • 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量等于 corePoolSize,但是任务队列 workQueue未满,那么任务被放入任务队列。
  • 如果此时线程池中的数量大于corePoolSize,任务队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量大于corePoolSize,任务队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

也就是:处理任务的优先级为:
核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

新任务添加到线程池流程简图


三、线程池的分类

Android中常见的有4中线程池,他们都是直接直接或者间接配置ThreadPoolExecutor来实现自己的功能特性的,他们分别是

  • FixedThreadPool
  • CacheThreadPool
  • SchedeledThreadPool
  • SingleThreadExecutor

这几种线程池的创建都是调用 java.util.concurrent.Executors 里面的方法的。


1、FixedThreadPool

  • 创建:
    • FixedThreadPool通过 newFixedThreadPool 方法来创建

corePoolsize等于maximumPoolSize

  • 特点:
    • 线程池里面线程数固定(只有核心线程,除非线程池被关闭要不然核心线程不会被回收)
    • 没有超时机制
    • 当线程池里面所有线程都处于活动状态时,新任务会处于等待状态,直到有新的线程能空闲出来。
    • 任务队列的大小没有限制



newFixedThreadPool 方法在 java.util.concurrent.Executors 里面

84     public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
85         return new ThreadPoolExecutor(nThreads, nThreads,
86                                       0L, TimeUnit.MILLISECONDS,
87                                       new LinkedBlockingQueue<Runnable>(),
88                                       threadFactory);
89     }

查看源码


2、CacheedThreadPool

  • 创建:

    • CacheedThreadPool 通过 newCachedThreadPool 方法来创建
  • 特点:

    • 线程池的核心线程数量不定,只有非核心线程
    • 最大线程数为 Integer.MAX_VALUE (相当于线程数可以任意多)
    • 当线程池中所有线程任务都处于活跃状态的时候,如果有新的任务请求执行,那么会创建的新的线程来处理新的任务。(CacheedThreadPool的任务队列相当于一个空集合,意味着任何任务都会被立即执行)
    • 线程池的任务都会有超时机制,时长为60秒,超过60秒的闲置线程就会被回收
  • CacheedThreadPool 常用场景:
    适合执行大量耗时耗时较少的任务,当整个线程池都处于闲置状态的时候,线程池中的任务会因为超时而被停止,所以他几乎是不占任何系统资源的。


    newCachedThreadPool 方法如下

59     public static ExecutorService newFixedThreadPool(int nThreads) {
60         return new ThreadPoolExecutor(nThreads, nThreads,
61                                       0L, TimeUnit.MILLISECONDS,
62                                       new LinkedBlockingQueue<Runnable>());
63     }


3、ScheduledThreadPool

  • 创建

    • 通过 newScheduledThreadPool 方法创建
  • 特点

    • 核心线程数固定,但是非核心线程数是没有限制的
    • 非核心线程闲置时会被激励回收
  • 使用场景:
    执行 定时任务具有固定周期重复任务


    newScheduledThreadPool 方法

Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically.
Parameters:
corePoolSize the number of threads to keep in the pool, even if they are idle.
Returns:
a newly created scheduled thread pool
Throws:
java.lang.IllegalArgumentException if corePoolSize < 0
217
218    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
219        return new ScheduledThreadPoolExecutor(corePoolSize);
220    }


4、SingleThreadExecutor

  • 创建
    • 通过 newSingleThreadExecutor 方法创建

*特点:
* 线程池内部只有一个核心线程,确保所有任务都在同一个线程中按顺序执行。
* 统一所有的外界任务到同一个线程,任务之间不需要处理线程同步的问题

newSingleThreadExecutor 方法:

Creates an Executor that uses a single worker thread operating off an unbounded queue. (Note however that if this single thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks.) Tasks are guaranteed to execute sequentially, and no more than one task will be active at any given time. Unlike the otherwise equivalent newFixedThreadPool(1) the returned executor is guaranteed not to be reconfigurable to use additional threads.
Returns:
the newly created single-threaded Executor
103
104    public static ExecutorService newSingleThreadExecutor() {
105        return new FinalizableDelegatedExecutorService
106            (new ThreadPoolExecutor(1, 1,
107                                    0L, TimeUnit.MILLISECONDS,
108                                    new LinkedBlockingQueue<Runnable>()));
109    }


4种线程池使用简单演示

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
        fixedThreadPool.execute(command);
        
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        cachedThreadPool.execute(command);
        
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
        // 2000ms后执行command
        scheduledThreadPool.schedule(command, 2000, TimeUnit.MILLISECONDS);
        // 延迟10ms后,每隔1000ms执行一次command
        scheduledThreadPool.scheduleAtFixedRate(command, 10, 1000, TimeUnit.MILLISECONDS);

        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(command);



参考:
《Android开发艺术探索》
线程池ThreadPoolExecutor使用简介

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容