第十一章、Android的线程和线程池
16lz
2021-01-23
第十一章、Android的线程和线程池
概述
- 从用途来说,线程分为主线程和子线程,主线程主要处理和界面相关的事情,而子线程则往往用于耗时操作。
- 在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统的资源,并且线程的创建和销毁都有相应的开销。
- 一个线程池会缓存一定数量的线程,通过线程池就可以避免因为频繁替换和销毁线程所带来的系统开销。Android中的线程池来源于Java,主要是通过Executor来派生特定类型的线程池。
1. 主线程和子线程
- 主线程是指进程所拥有的线程。主线程主要处理界面交互相关的逻辑,不能处理耗时的任务。除了主线程以外的线程都是子线程。
2. Android中的线程形态
除了传统的Thread以外,还包含AsyncTask、HandlerThread以及IntentService,这三者的底层实现也是线程。
1. AsyncTask:
- 从实现上来说,AsyncTask封装了Thread和Handler。
- AsyncTask的类必须在主线程中加载,这就意味着第一次访问AsyncTask必须发生在主线程,
- AsyncTask的对象必须在主线程中创建。
- execute方法必须在UI线程调用。
2.AsyncTask的工作原理:
- 首先系统会把AsyncTask的Params参数封装为FutureTask对象,FutureTask是一个并发类,充当了Runnable的作用。
- SerialExecutor的execute方法首先会吧FutureTask对象插入到任务队列中。如果此时没有正在活动的任务,那么就会执行下一个AsyncTask任务。同时一个任务执行完成后,会继续执行下一个,直到所有的任务执行完成。
- AsyncTask有两个线程池SerialExecutor和THREAD_POOL_EXECUTOR和一个handler(InternalHandler)。SerialExecutor用于任务的排队,THREAD_POOL_EXECUTOR用于真正的执行任务。InternalHandler用于将执行环境从线程池切换到主线程。
- FeatureTask对象会间接调用AsynTask的doInBackGroud方法,将结果传递给postResult。
- postResult会通过sHandler发送消息。sHandler是一个静态的Handler对象,sHandler对象必须在主线程中创建。
- sHandler收到MESSAGE_POST_RESULT消息后会调用AsyncTask的finish方法。
3. HandlerThread:
- HandlerThread继承了Thread,它是一种可以使用Handler的Thread,它的实现方式是,在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。
- 它和普通的Thread的区别是:普通Thread主要用于在run方法中执行一个耗时任务,HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。一个具体的使用场景是IntentService。HandlerTherad的run方法是一个无限循环,因此确定不需要时,通过它的quit或者quitSafely方法来终止线程的执行。
4. IntentService
- IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,因此必须创建它的子类才能使用。
- IntentService第一次启动时,会创建HandlerThread,然后通过Looper构建一个Handler对象mServiceHandler,这样通过mServiceHandler发送的消息最终都会在HandlerThread中执行。
- mServiceHandler收到消息后,会将Intent传递给onHandleIntent方法去处理。onHandleIntent是一个抽象方法,它需要我们在子类中实现。
- Handler中的Looper是顺序处理消息的,意味着IntentService也是顺序执行后台任务的。
3. Android中的线程池
线程池的好处:
1. 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
2. 有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系 统资源导致的阻塞现象。
3. 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
1. ThreadPoolExecutor
ThreadPoolExecutor是线程池真正的实现,构造方法提供了一些列参数来配置线程池。
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
- corePoolSize:
线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔有keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时常后,核心线程就会被终止。 - maximumPoolSize:
线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后序的新任务会被阻塞。 - keepAliveTime:
非核心线程闲置时的超时时常,超过这个时常,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。 - unit:
用于执行keepAliveTime参数的时间单位,这是一个枚举,常用的TimeUnit.MILLISECONEDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)。 - workQueue:
线程池中的任务队列,通过线程池的execute方法提交的runnable对象会存储在这个参数中。 - threadFactory:
线程工厂,为线程池提供创建新线程的功能。 - RejectedExecutionHandler :
不常用,线程池无法执行新任务时,可能是由于任务队列已满或者是无法成功执行任务。会调用handler的rejectedExecution方法来通知调用者。
- corePoolSize:
ThreadPoolExecutor执行任务的大致顺序:
- 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
- 如果线程池中的线程数量已经达到了或者超过了核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
- 如果在步骤2中无法将任务队列插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行。
- 如果步骤3中的线程数量已经达到线程池规定的最大值,那么就拒绝此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。
AsyncTask的THREAD_POOL_EXECUTOR线程池的配置:
- corePoolSize=CPU核心数+1;
- maximumPoolSize=2倍的CPU核心数+1;
- 核心线程无超时机制,非核心线程在闲置时间的超时时间为1s;
- 任务队列的容量为128。
Android中常见的4类具有不同功能特性的线程池:
- FixedThreadPool:线程数量固定的线程池,它只有核心线程;
- CachedThreadPool:线程数量不固定的线程池,它只有非核心线程;
- ScheduledThreadPool:核心线程数量固定,非核心线程数量没有限制的线程池,主要用于执行定时任务和具有固定周期的任务;
- SingleThreadPool:只有一个核心线程的线程池,确保了所有的任务都在同一个线程中按顺序执行。
更多相关文章
- Android中的线程模型,
- [转载]Android中的线程模型
- android异步线程利用Handler将消息发送至UI线程
- Android消息机制原理,仿写Handler Looper源码跨线程通信原理--之
- Android核心分析 之一--------分析方法论探讨之设计意图
- Android UI 线程更新UI也会崩溃???
- Android创建子线程和回调主线程的几种方式
- android 照个图 要开线程 不然永远拍的图都是那么的小