除了Thread本身以外,在Android中可以扮演线程的角色还有很多,比如AsyncTask和IntentService,同时HandlerThread也是一种特殊的线程。尽管AsyncTask,IntentService以及HandlerThread的表现形式都有别于传统的线程,但是它们的本质任然是传统的线程。
对于AsyncTask来说,它的底层用到线程池,对于IntentService和HandlerThread来说,它们的底层则直接使用了线程。
不同形式的线程虽然都是线程,但是具有不同的特性和使用场景。AsyncTask封装了线程池和Handler,它主要是为了方便开发者在子线程中更新UI。
HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler。
IntentService是一个服务,系统对其进行了封装使其可以方便地执行后台任务,IntentService内部采用HandlerThread来执行任务,当任务执行完成之后,IntentService会自动退出。
默认只有一个线程,就是主线程,主要处理和界面交互相关的逻辑,子线程也叫工作线程,除了主线程以外的线程都是子线程。
分别介绍AsyncTask,HandlerThread以及IntentService。
AsyncTask是一种轻量级的异步任务类,它可以在线程池中,执行后台任务,然后把执行的进度和最终的结果传递给主线程,并在主线程中更新UI。从实现上面来说,AsyncTask封装了Thread和Handler,通过AsyncTask可以更加方便地执行后台任务以及在主线程中访问UI,但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。
AsyncTask是一个抽象的泛型类,提供了Params(参数类型),Progress(进度类型)和Result(后台任务返回的结果类型)这三个泛型参数。
AsyncTask提供了四个核心方法:
(1)onPreExecute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作。
(2)doInBackground(Params… params),在线程池中执行,此方法用于执行异步任务,params参数表示异步任务的输入参数。在此方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法会调用onProgressUpdate方法,另外此方法需要返回计算结果给onPostExecute方法。
(3)onProgressUpdate(Progress…values),在主线程中执行,当后台任务的执行进度发生变化时此方法会被调用。
(4)onPostExecute(Result result)在主线程中执行,在异步任务执行之后,此方法会被调用,其中result是后台任务的返回值,即doInBackground的返回值。
注意:
(1)AsyncTask的类必须在主线程中加载,这就意味着第一次访问AsyncTask必须发生在主线程,当然这个过程在Android 4.1以及以上的版本以及被系统自动完成。
(2)AsyncTask的对象必须在主线程中创建。
(3)execute方法必须在UI线程中调用。
(4)不要在程序中直接调用onPretExecute,onPostExecute,doInBackground和onProgressUpdate方法。
(5)一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常,就是说执行同样的任务,每次还是需要new对象。
(6)在Android 1.6之前,AsyncTask是串行执行任务的,Android 1.6的时候AsyncTask开始采用线程池处理并行任务,但是从Android 3.0开始,为了避免AsyncTask所带来并发错误,AsyncTask又采用一个线程来串行执行任务。尽管如此,在Android 3.0以后,我们任然可以通过AsyncTask的executeOnExec方utor法来并行地执行任务。
HandlerThread继承了Thread,它是一种可以使用Handler的Thread,它的实现也很简单,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了。
继承了Service并且是一个抽象类,可以用于执行耗时任务,当任务执行后会自动停止。
线程池的优点:
(1)重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销;
(2)能有效控制线程池的最大并发数,避免大量线程之间因互相抢占系统资源而导致的阻塞现象;
(3)能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能;
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);
}
corePoolSize:线程池的核心线程数,核心线程会在线程池中一直存活,即使它们处于闲置状态。
如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超市策略,这个时间由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。
maximumPoolSize:线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续新任务将会阻塞。
keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性为true时,keepAliveTime同样会作用于核心线程。
unit:用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒),TimeUnit.SECONDS(秒),以及TimeUnit.MINUTES(分钟)等。
workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。
threadFactory:线程工厂,为线程池提供新建线程的功能,ThreadFactory是一个接口,只有一个方法,Thread newThread(Runnable r)。
除了上面的主要参数外,ThreadPoolExecutor还有一个不常用的参数RejectedExecutionHandler。当线程池无法执行新任务时,这可能是由于任务队列已满或者是无法成功执行任务,这个时候ThreadPoolExecutor会调用handler的rejectedException方法来通知调用者,默认情况下,rejectedException会直接抛出一个RejectedExcecutionException。
ThreadPoolExecutor为RejectedExecutionHandler提供了几个可选值:
CallerRunsPolicy,AbortPolicy,DiscardPolicy,DiscardOldstPolicy,其中AbortPolicy是默认值,会直接抛出RejectedExcecutionException。
ThreadPoolExecutor执行任务大致遵循下面的规则:
(1)如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务;
(2)如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行;
(3)如果在第二步中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务;
(4)如果第三步中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者;
介绍四类具有不同功能特性的线程池。
通过Executors的newFixedThreadPool是一种线程数量固定的线程池,当线程处于空闲状态时,它们并不会回收,除非线程池关闭了。
当所有线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
通过Executors的newCachedThreadPool方法来建立。它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数量未Integer.MAX_VALUE。
从CachedThreadPool的特性来看,这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而停止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎是不占用任何系统资源的。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通过Executors的newScheduledThreadPool方法来创建。它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收。主要用于执行定时任务和具有固定周期的重复任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
通过Executors的newSingleThreadExecutors方法来创建。只有一个核心线程,确保所有的任务都在同一个线程中按顺序执行。意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用:
Runnable command = new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
}
};
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
fixedThreadPool.execute(command);
ExecutorService cachedThreadPool = Executors.newFixedThreadPool(3);
cachedThreadPool.execute(command);
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
// 延迟10ms,每隔1000ms执行一次
scheduledThreadPool.scheduleWithFixedDelay(command, 10, 1000, TimeUnit.MILLISECONDS);
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(command);