此篇为《Andoroid开发艺术探索》第11章Android的线程和线程池的读书笔记
除了Thread本身以外,在Android中可以扮演线程角色的还有很多,比如AsyncTask和IntentService,同时HandlerThread也是一种特殊的线程。对于AsyncTask来说,它的底层用到了线程池,对于IntentService和HandlerThread来说,它们的底层则直接使用了线程。
不同形式的线程虽然都是线程,但是它们仍然具有不同的特性和使用场景。AsyncTask封装了线程池和Handler,它主要是为了方便开发者在子线程中更新UI。HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler。IntentService是一个服务,系统对其进行了封装使其可以更方便地执行后台任务,IntentService内部采用HandlerThread来执行任务,当任务执行完毕后IntentService会自动退出。从任务执行的角度来看,IntentService的作用很像一个后台线程,但是IntentService是一种服务,它不容易被系统杀死从而可以尽量保证任务的执行,而如果是一个后台线程,由于这个时候进程中没有活动的四大组件,那么这个进程的优先级就会非常低,会很容易被系统杀死,这就是IntentService的有点。
线程是系统调度的最小单元,线程是一种受限的系统资源,它的创建和销毁都会有相应的开销。当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程,因为线程不可能做到绝对的并行。通过线程池可以避免因为频繁创建和销毁线程所带来的系统开销,一个线程池中会缓存一定数量的线程。Android中的线程池来源于java,主要通过Executor来派生特定类型的线程池,不同种类的线程池又具有各自的特性。
11.1 主线程和子线程
主线程是指进程所拥有的线程,在Java中默认情况下一个进程中只有一个线程,这个线程就是主线程。除了主线程以外的线程都是子线程,子线程也叫工作线程。
Android沿用了Java的线程模式。其中主线程也叫UI线程,主线程的作用是运行四大组件以及处理它们和用户的交互,而子线程的作用则是执行耗时任务,比如网络请求、I/O操作等。从Android3.0开始系统要求忘了访问必须在子线程中进行,否则网络访问将会失败并抛出NetworkOnMainThreadException这个异常,这样做是为了避免主线程由于被耗时操作所阻塞从而出现ANR现象。
11.2 Android中的线程形态
除了传统的Thread以外,还包含AsyncTask、HandlerThread以及IntentService,这三者的底层实现也是线程,但是它们具有特殊的表现形式,同时在使用上也各有优缺点。
11.2.1 AsyncTask
AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程中更新UI。从实现上来说,AsyncTask封装了Thread和Handler,通过AsyncTask可以更加方便地执行后台任务以及在主线程中访问UI,但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。
AsyncTask类声明和参数说明如下:
/** * AsyncTask是一个抽象的泛型类 * Params:参数的类型 * Progress:后台任务的执行进度的类型 * Result:后台任务返回结果的类型 * 如果不需要传递具体的参数,这三个泛型参数可以用Void来代替 */ public abstract class AsyncTask<Params, Progress, Result> |
AsyncTask提供了4个核心方法,相关说明如下所示:
private class AsyncDemo extends AsyncTask<Integer, Integer, String> {
/** * 主线程中执行,异步任务执行之前调用,可以用于做一些准备工作 */ @Override protected void onPreExecute() { super.onPreExecute(); } /** * 线程池中执行,用于执行异步任务,params表示异步任务的输入参数 * 通过调用publishProgress()来更新任务的进度,publishProgress会调用onProgressUpdate * 此外这个方法需要return计算结果给onPostExecute()方法 */ @Override protected String doInBackground(Integer... params) { return null; } /** * 主线程中执行,当后台任务的执行进度发生改变时改方法会被调用 */ @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } /** * 主线程中执行,异步任务执行之后该方法被调用 * result为doInBackground的返回值 */ @Override protected void onPostExecute(String result) { super.onPostExecute(result); } /** * 主线程中执行,当异步任务被取消时该方法被调用 */ @Override protected void onCancelled() { super.onCancelled(); } } |
当要执行异步任务的时候,可通过AsyncTask的execute()方法开启异步任务。
AsyncTask的条件限制:
1) AsyncTask的类必须在主线程中加载。
2) AsyncTask的对象必须在主线程中创建。
3) execute方法必须在UI线程调用
4) 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常
5) 从Android3.0开始,AsyncTask采用一个线程来串行执行任务。我们可以通过executeOnExecutor方法来并行执行任务。
11.2.2 AsyncTask的工作原理
AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正的执行任务,InternalHandler用于将执行环境从线程池切换到主线程。
InternalHandler是一个静态的Handler对象,为了能够将执行环境切换到主线程,这就要求InternalHandler这个对象必须在主线程中创建。由于静态成员会在加载类的时候进行初始化,因此这就变相要求AsyncTask的类必须在主线程中加载,否则同一个进程中的AsyncTask都将无法正常工作。
11.2.3 HandlerThread
HandlerThread继承了Thread,它是一种可以使用Handler的Thread。
HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。HandlerThread的run方法是一个无限循环,当不需要使用HandlerThread时,可以通过它的quit或quitSafely方法来终止线程的执行。
11.2.4 IntentService
IntentService继承了Service并且是一个抽象类。IntentService可用于执行后台耗时的任务,任务执行后它会自动停止。IntentService的优先级比单纯的线程要高很多,所以IntentService比较适合执行一些高优先级的后台任务,因为它的高优先级不容易被后台杀死。
11.3 Android中的线程池
线程池的优点:
1、 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
2、 能有效控制线程池的最大并发数,避免大量的线程之间因互抢占资源而导致的阻塞现象。
3、 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。
11.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
非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当allowCoreThreadTimeOut设置为true时,keepAliveTime同样会作用于核心线程。
unit
用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。
workQueue
线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。
threadFactory
线程工厂,为线程池提供创建新线程的供。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)。
ThreadPoolExecutor执行任务时大致遵循如下规则:
1、如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
2、如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
3、如果在步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
4、如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。
11.3.2 线程池的分类
Android中最常见的四类具有不同功能特性的线程池,都是直接或间接地通过配置ThreadPoolExecutor来实现自己的功能特性。
1、FixedThredPool
通过Executors的newFixedThreadPool方法来创建。它是一种线程数量固定的线程池,当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。由于FixedThreadPool只有核心线程并且这些核心线程不会被回收,这意味着它能够更加快速地响应外界的请求。FixedThreadPool中只有核心线程并且这些核心线程没有超时机制,另外任务队列也没有大小限制。
public static ExecutorService newFixedThreadPool(int nThreads) { |
2、CachedThreadPool
通过Executors的newCachedThreadPool方法来创建。它是一种线程数量不定的线程池,它只有非核心线程。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程的超时时长为60秒,超过60秒闲置线程就会被回收。CachedThreadPool的任何任务都会被立即执行。这类线程比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被停止,这个时候CacheThreadPool之中实际上是没有任何线程的。
public static ExecutorService newCachedThreadPool() { |
3、ScheduledThreadPool
通过Executors的newScheduledThreadPool方法来创建。它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收。这类线程主要用于执行定时任务和具有固定周期的重复任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { public static ScheduledExecutorService newScheduledThreadPool( |
4、SingleThreadExecutor
通过Executors的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。它的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。
public static ExecutorService newSingleThreadExecutor() { |
目前先这样,后续会贴上demo代码