线程
概念
说到线程池,不得不说一下线程。无论是 java
还是 Android
,线程都是一个非常重要的概念,它是所有基础操作的载体,无论是更新 UI
,或是请求网络等耗时操作,都需要在线程中完成。众所周知,Android
中将线程分为 主线程 和 工作线程。那主线程和工作线程有什么区别的,其实本质上没太大区别,主线程因为是要跟用户直接打交道,实时交互性强,不能有其他的耗时操作阻塞其正常流程,不然出现丢帧卡顿的现象,因此 Android
是禁止在主线程中进行耗时操作的。
Thread
单纯的线程操作很简单,只需要:1
2
3
4
5
6new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
就可以将耗时操作放在子线程中执行。
Handler
如果涉及到与主线程数据的交互,比如需要在子线程中更新 UI 的话,最常见的就是通过 Handler
的方式来通知主线程更新 UI。
1 | static class MyHandler extends Handler { |
考虑到 内部类对外部类持有引用,可能引发内存泄漏问题,所以采用静态内部类+持有外部类的弱引用方式来解决此问题。像类似这种常写的代码,通过创建 AS 模板,在下次需要使用的时候直接使用,方便快捷。
AsyncTask
作为轻量级别的异步任务类,内部封装了线程池、线程和 Handler
,主要的流程:
- 耗时操作之前准备 (Main Thread)
- 处理耗时操作 & 向主线程发送更新进度的
message
(Work Thread) - 获取进度的回调并处理 (Work Thread)
- 耗时操作结束的处理 (Main Thread)
- (如果调用cancel),则要处理取消后的相应操作 (Main Thread)
作为抽象类的 AsyncTask
,需要定义相应的泛型:
1 | // Params 参数类型 |
主要涉及到的四个核心方法:
- onPreExecute(): 在主线程处理一些准备工作。
- doInBackground(Params…params): 在子线程中处理异步耗时任务,可以通过
publishProgress
方法来更新任务的进度。 - onProgressUpdate(Progress…values): 在主线程中执行,当后台任务进度改变触发回调。
- onPostExecute(Result result): 在主线程中,异步任务结束触发回调,其中 result 就是后台任务的返回值。
案例
咱们快速实现一个简单小例子,主要功能:输入一串字符数组,统计所以字符的长度。为了模拟耗时操作,在 doInBackground
方法里让 Thread
睡一会。
1 | static class CalculateSizeTask extends AsyncTask<String, Float, Long> { |
与之前 Handler
创建方式一样,采用 静态内部类+外部类的弱引用 方式避免内存泄漏。最终调用就简单多了。
1 | calculateSizeTask = new CalculateSizeTask(this); |
看一下效果:
效果很明显不做过多解释,不过有几点需要注意一下:
- 创建
AsyncTask
对象过程 &execute
执行过程必须在主线程中完成。 - 不要调用
AsyncTask
内部的回调方法(doInBackground
…)。 - 不能多次执行同一个
AsyncTask
对象的execute
方法,否则会直接抛出异常。 - 因为
AsyncTask
内部默认是串行执行任务,因此前一个任务没有执行完,新的任务处于等待过程。
IntentService
Service
用于处理长期需要在后台执行的任务,但是不代表着可以执行耗时任务,内部的操作其实都是在主线程中完成的。因此 IntentService
诞生,既可以存在于后台,提高任务的存活率,又可以执行耗时操作。相比较于前者 AsyncTaks
它毕竟需要依附于 Activity
,因此生命周期相对较短,而 IntentService
内部封装了 HanderThread
和 Handler
。
HandlerThread
是 Thread
的子类,具体的逻辑都在 run()
方法中:
1 |
|
其实就是内部维护了一个无线循环的消息队列,这样就可以在 HandlerThread
中创建 Handler
,外部则通过消息的方式来通知 HandlerThread
执行一个具体的任务。
再回到 IntentService
, 在 onCreate()
方法中创建 HandlerThread
和 Handler
。
1 | @Override |
随后在 onStart()
方法中创建 Message
交给 Handler
处理,Handler
收到事件通知后调用需要子类覆盖的 onHandleIntent()
方法,具体的耗时任务就在该方法中执行,执行结束调用 stopSelf()
关闭自己。这套流程行云流水,丝毫不拖泥带水,默默的执行,默默的关闭,都不用关心 Service
的生命周期。
当需要同时执行多个任务时,IntentService
会串行执行,直至任务完成才会关闭服务。
线程池
当需要执行的任务增多时,单个线程是满足不了需求的,此时就需要创建多个线程来完成需要。多线程的最大好处就在于 提高 CPU 的利用率 & 提高执行效率,同时也存在着一些弊端:频繁的创建和销毁线程会产生很多的性能开销。为了解决这个问题,线程池孕育而生。
- 复用线程池中的线程,减少创建&销毁线程的性能开销。
- 控制线程的并发数,避免对资源竞争而导致阻塞现象。
- 对线程有很好的管理并开启新的姿势。
ThreadPoolExecutor
翻译过来就是 线程池执行器,它是线程池的真正实现,构造方法提供了一些列参数来配置线程池,掌握了这些参数的配置,可以加大对线程池的了解。
1 | public ThreadPoolExecutor(int corePoolSize, |
以这个最常见的构造函数进行分析。
- corePoolSize: 线程池中核心的线程数。默认情况核心线程是一致存活,及时没有任何操作。但是如果执行了
allowCoreThreaTimeOut()
方法,核心线程也是有可能被回收的,这取决于是否处于闲置状态 &keepAliveTime
。 - maximumPollSize: 线程池所能容纳的最大线程数。超过限制,新线程会被阻塞。
- keepAliveTime: 线程闲置超时等待时间。超过此值会被回收。
- unit: 超时等待时间单位。
- workQueue: 线程池中的任务队列。每次执行
execute()
会把runnable
对象存储在这个队列中。 - threadFactory: 线程工厂,为线程池提供创建新线程的功能。
关键点在于 核心线程数、最大线程数和任务队列数,执行流程如下,记住一点,优先级:核心线程数 > 任务队列数 > 最大线程数。
- 线程池中线程数小于核心线程数,直接创建新的核心线程。
- 如果线程数超过了超过核心线程数,任务会被插入到任务队列中等待执行。
- 如果任务队列也满了,这时会进入到最大线程(步骤4)的判断逻辑值。
- 如果线程池中线程数小于最大线程数,则启动一个非核心线程来执行。
- 如果果线程池中线程数超过了最大线程数,那没办法了,任务会被拒绝,
ThreadPoolExecutor
会调用 RejectedExecutionHander 的 rejectedExecution 方法通知调用者。
Executors
Executors
提供了创建常用线程池的静态方法,接下也会大概讲解一下常用的四种线程池。
- 定长线程池 (FixedThreadPool)
- 单线程化线程池 (SingleThreadExecutor)
- 定时线程池 (ScheduledThreadPool)
- 缓存线程池 (CachedThreadPool)
在创建线程池之前,我单独构造了个方法专门用来创建 Runnnable
对象。
1 | private Runnable getRunnable() { |
该 Runnable
对象主要用来打印线程信息,并展示在界面上。
定长线程池 (FixedThreadPool)
源码:
1 | public static ExecutorService newFixedThreadPool(int nThreads) { |
特点:
- 线程数量固定
- 只有核心线程,并且不会被回收
- 任务队列无限制,超出核心线程数的线程处于等待中
- 适用于控制线程的最大并发数
案例:
1 | private void fixedThreadPool() { |
效果:
这里我设置了最大并发数为2 ,所以只会创建两个核心线程,其余为完成的需要进行等待。
单线程化线程池 (SingleThreadExecutor)
源码:
1 | public static ExecutorService newSingleThreadExecutor() { |
特点:
- 只有一个核心线程
- 任务队列无限制
- 不需要考虑线程同步问题
- 适用于一些 因为并发而导致问题的操作
案例:
1 | private void singleThreadPool() { |
效果:
简单粗暴,只有一个线程在执行。
定时线程池 (ScheduledThreadPool)
源码:
1 | public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { |
特点:
- 线程数量固定
- 非核心数量无限制
- 适用于定时&周期性任务
案例:
1 | private void scheduledThreadPool() { |
schedule(): 执行定时任务
scheduleAtFixedRate(): 执行周期任务 (从任务开始计时)
scheduleWithFixedDelay(): 执行周期任务 (从任务结束开始计时)
效果:
可缓存线程池 (CachedThreadPool)
源码:
1 | public static ExecutorService newCachedThreadPool() { |
特点:
- 无核心线程
- 非核心线程数量无限制
- 对于空闲线程回收灵活
- 适用于大量&耗时少的任务
案例:
1 | private void cacheThreadPool() { |
效果:
可以看到很多线程是被复用了。
相关代码已经提交。ThreadPoolDemo
感谢
Android 开发艺术探索