进阶之路 | 奇妙的Thread之旅
许朋友爱玩 人气:1
## 前言
> 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍:
>
> [我的GIthub博客](https://lovelifeeveryday.github.io/)
## 需要已经具备的知识:
- `Thread`的基本概念及使用
- `AsyncTask`的基本概念及使用
## 学习导图:
![学习导图](https://cdn.jsdelivr.net/gh/LoveLifeEveryday/FigureBed@master/typora202003/09/153149-457927.png)
## 一.为什么要学习`Thread`?
在`Android`中,几乎完全采用了`Java`中的线程机制。线程是最小的调度单位,在很多情况下为了使`APP`更加流程地运行,我们不可能将很多事情都放在主线程上执行,这样会造成严重卡顿(`ANR`),那么这些事情应该交给子线程去做,但对于一个系统而言,创建、销毁、调度线程的过程是需要开销的,所以我们并不能无限量地开启线程,那么对线程的了解就变得尤为重要了。
本篇文章将带领大家由浅入深,从**线程的基础**,谈到**同步机制**,再讲到**阻塞队列**,接着提及**`Android`中的线程形态**,最终一览**线程池机制**。
话不多说,赶紧开始奇妙的`Thread`之旅吧!
## 二.核心知识点归纳
### 2.1 线程概述
Q1:**含义**
线程是`CPU`调度的最小单位
> 注意与进程相区分
Q2:**特点**
线程是一种**受限**的系统资源。即线程不可无限制的产生且线程的创建和销毁都有一定的开销
>**Q**:如何避免频繁创建和销毁线程所带来的系统开销?
>**A**:采用**线程池**,池中会缓存一定数量的线程,进而达到效果
Q3:**分类**
- 按用途分为两类:
> - **主线程**:一般一个进程只有一个主线程,主要处理**界面交互**相关的逻辑
>
> - **子线程**:除主线程之外都是子线程,主要用于执行**耗时操作**
- 按形态可分为三类:
> - `AsyncTask`:底层封装了线程池和`Handler`,便于执行后台任务以及在主线程中进行`UI`操作
> - `HandlerThread`:一种具有**消息循环**的线程,其内部可使用`Handler`
> - `IntentService`:一种**异步、会自动停止**的服务,内部采用`HandlerThread`和`Handler`
![关系图](https://cdn.jsdelivr.net/gh/LoveLifeEveryday/FigureBed@master/typora202003/08/142029-69495.png)
PS:想详细了解`Handler`机制的读者,推荐一篇笔者的文章:[进阶之路 | 奇妙的Handler之旅](https://juejin.im/post/5e61bf2de51d4526ea7f00bd)
Q4:**如何安全地终止线程?**
>对于有多线程开发经验的开发者,应该大多数在开发过程中都遇到过这样的需求,就是在某种情况下,希望立即停止一个线程
>
>比如:做`Android`开发,当打开一个界面时,需要开启线程请求网络获取界面的数据,但有时候由于网络特别慢,用户没有耐心等待数据获取完成就将界面关闭,此时就应该立即停止线程任务,不然一般会内存泄露,造成系统资源浪费,如果用户不断地打开又关闭界面,内存泄露会累积,最终导致内存溢出,`APP`闪退
>
>所以,笔者希望能和大家探究下:如何安全地终止线程?
A1:**为啥不使用`stop`?**
> Java官方早已将它废弃,不推荐使用
- `stop`是通过立即抛出`ThreadDeath`异常,来达到停止线程的目的,此异常抛出有可能发生在任何一时间点,包括在`catch`、`finally`等语句块中,但是此异常并不会引起程序退出
- 异常抛出,导致线程会**释放**全部所持有的**锁**,极可能引起**线程安全**问题
A2:**提供单独的取消方法来终止线程**
示例`DEMO`:
```java
public class MoonRunner implements Runnable {
private long i;
//注意的是这里的变量是用volatile修饰
volatile boolean on = true;
@Override
public void run() {
while (on) {
i++;
}
System.out.println("sTop");
}
//设置一个取消的方法
void cancel() {
on = false;
}
}
```
> 注意:这里的变量是用`volatile`修饰,以保证**可见性**,关于`volatile`的知识,笔者将在下文为您详细解析
A3:**采用`interrupt`来终止线程**
`Thread`类定义了如下关于中断的方法:
![中断的方法](https://s2.ax1x.com/2020/03/08/3xQq4s.png)
原理:
- 调用`Thread`对象的`interrupt`函数并不是立即中断线程,只是将线程**中断**状态**标志**设置为`true`
- 当线程运行中有调用其阻塞的函数时,阻塞函数调用之后,会不断地轮询检测中断状态标志是否为`true`,如果为`true`,则停止阻塞并抛出`InterruptedException`异常,同时还会重置中断状态标志,因此需要在`catch`代码块中需调用`interrupt`函数,使线程再次处于中断状态
- 如果中断状态标志为`false`,则继续阻塞,直到阻塞正常结束
>具体的`interrupt`的使用方式可以参考这篇文章:[Java线程中断的正确姿势](https://www.jianshu.com/p/264d4e1b76af)
### 2.2 同步机制
#### 2.2.1 `volatile`
> - 有时候仅仅为了读写一个或者两个实例就使用同步`synchronized`的话,显得开销过大
> - 而`volatile`为实例域的同步访问提供了免锁的机制
Q1:**先从`Java`内存模型聊起**
- `Java` 内存模型定义了**本地内存和主存**之间的抽象关系
> - 线程之间的**共享变量**存储在**主存**中
> - 每个线程都有一个**私有的本地内存**(工作内存),本地内存中存储了该线程共享变量的**副本**。
![内存关系](https://i.bmp.ovh/imgs/2019/11/9c5f1de764bb8c47.png)
- 线程之间通信的步骤
> - 线程**A**将其**本地内存**中**更新过的共享变量刷新到主存**中去
> - 线程**B**到**主存**中去**读取**线程A之前已**更新过的共享变量**
Q2:**`原子性、可见性和有序性`了解多少**
a1:**原子性`Atomicity`**:
- 定义:原子性操作就是指这些操作是不可中断的,要做一定做完,要么就没有执行
- 对基本数据类型变量的**读取和赋值**操作是原子性操作
> 注意:这里的**赋值**操作是指**将数字赋值给某个变量**
下面由`DEMO`解释更加通俗易懂
```java
x=3; //原子性操作
y=x; //非原子性操作 原因:包括2个操作:先读取x的值,再将x的值写入工作内存
x++; //非原子性操作 原因:包括3个操作:读取x的值、对x的值进行加1、向工作内存写入新值
```
- `volatile`不支持原子性(想探究原因的,笔者推荐一篇文章:[面试官最爱的volatile关键字](https://juejin.im/post/5a2b53b7f265da432a7b821c#heading-2))
- 保证整块代码原子性(例如`i++`)的方法:借助于`synchronized`和`Lock`,以及并发包下的`atomic`的原子操作类
a2:**可见性`Visibility`**
- 定义:一个线程修改的结果,另一个线程马上就能看到
- `Java`就是利用`volatile`来提供可见性的
> 原因:当一个变量被`volatile`修饰时,那么对它的修改会**立刻刷新到主存**,同时使**其它线程的工作内存**中对此变量的**缓存行失效**,因此需要读取该变量时,会去内存中读取新值
- 其实通过`synchronized`和`Lock`也能够保证可见性,但是`synchronized`和`Lock`的开销都更大
a3:**有序性`Ordering`**
- **指令重排序**的定义:大多数现代微处理器都会采用将指令乱序执行的方法, 在条件允许的情况下, 直接运行当前有能力立即执行的后续指令, 避开获取下一条指令所需数据时造成的等待
- 什么时候不进行**指令重排序**:
> - 符合数据依赖性:
>
> ```java
> //x对a有依赖
> a = 1;
> x = a;
> ```
>
> - `as-if-serial`语义:不管怎么重排序, 单线程程序的执行结果不能被改变
> - 程序顺序原则
>
> 1. 如果A `happens-before` B
> 2. 如果B `happens-before` C
> 3. 那么A `happens-before` C
>
> 这就是`happens-before`传递性
- `volatile`通过**禁止指令重排序**的方式来保证有序性
Q3:**应用场景有哪些?**
- 状态量标记
> 线程的终止的时候的状态控制,示例DEMO如前文
- `DCL`
> 避免指令重排序:
>
> 假定创建一个对象需要:
>
> 1. 申请内存
> 2. 初始化
> 3. `instance`指向分配的那块内存
>
> 上面的2和3操作是有可能重排序的, 如果3重排序到2的前面, 这时候2操作还没有执行, `instance!=null`, 当然不是安全的
```java
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
```
Q4:原理:
- 如果把加入`volatile`关键字的代码和未加入`volatile`关键字的代码都生成汇编代码,会发现加入`volatile`关键字的代码会多出一个`lock`前缀指令
- `lock`前缀指令实际相当于一个**内存屏障**,内存屏障提供了以下功能:
> - 重排序时不能把后面的指令重排序到内存屏障之前的位置
> - 使得本`CPU`的`Cache`写入内存
> - 写入动作也会引起别的`CPU`或者别的内核无效化其`Cache`,相当于让新写入的值对别的线程可见
#### 2.2.2 重入锁与条件对象
> `synchronized` 关键字自动为我们提供了锁以及相关的条件,大多数需要显式锁的时候,使用`synchronized` 非常方便,但是当我们了解了重入锁和条件对象时,能更好地理解`synchronized` 和阻塞队列
Q1:**重入锁的定义**
- 可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁
- `ReentrantLock`和`synchronized`都是可重入锁
> 重复调用锁的`DEMO`如下:
```java
public class ReentrantTest implements Runnable {
public synchronized void get() {
System.out.println(Thread.currentThread().getName());
set();
}
public synchronized void set() {
System.out.println(Thread.currentThread().getName());
}
public void run() {
get();
}
public static void main(String[] args) {
ReentrantTest rt = new ReentrantTest();
for(;;){
new Thread(rt).start();
}
}
}
```
Q2:什么是条件对象`Condition`?
- 条件对象来管理那些已经**获得了一个锁**但是却**不能做有用工作**的线程,条件对象又被称作条件变量
- 一般要配合`ReentrantLock`使用,用`Condition.await()`可以**阻塞当前线程**,并**放弃锁**
Q3:下面说明重入锁与条件对象如何协同使用
> - 用**支付宝转账**的例子(支付宝打钱,狗头.jpg)
> - 场景是这样的:
```java
//转账的方法
public void transfer(int from, int to, int amount){
//alipay是ReentrantLock的实例
alipay.lock();
try{
//当要转给别人的钱大于你所拥有的钱的时候,调用Condition的await可以阻塞当前线程,并放弃锁
while(accounts[from] < amount){
condition.await();
}
...//一系列转账的操作
//阻塞状态解除,进入可运行状态
condition.signalAll();
}
finally{
alipay.unlock();
}
}
```
> 想要更深一步了解重入锁的读者,可以看下这篇文章:[究竟什么是可重入锁?](https://blog.csdn.net/rickiyeat/articlehttps://img.qb5200.com/download-x/details/78314451)
#### 2.2.3 `synchronized`
Q1:`synchronized`有哪几种实现方式?
- 同步代码块
- 同步方法
Q2:`synchronized`与`ReentrantLock`的关系
- 两者都是重入锁
- 两者有些方法互相对应
> - `wait`等价于`condition.await()`
> - `notifyAll`等价于`condition.signalAll()`
Q3:使用场景对比
| 类型 | 使用场景 |
| ---------------- | ------------------------------------ |
| 阻塞队列 | 一般实现同步的时候使用 |
| 同步方法 | 如果同步方法适合你的程序 |
| 同步代码块 | 不建议使用 |
| `Lock/Condition` | 需要使用`Lock/Condition`的独有特性时 |
### 2.3 阻塞队列
> 为了更好地理解线程池的知识,我们需要了解下阻塞队列
Q1:**定义**
- 阻塞队列`BlockingQueue`是一个支持两个附加操作的队列。这两个附加的操作是:
> - 在队列为空时,获取元素的线程会等待队列变为非空
>- 当队列满时,存储元素的线程会等待队列可用
Q2:**使用场景**:
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
Q3:**核心方法**
| 方法\处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
| :------------ | :---------- | :--------- | :------- | :------------------- |
| 插入方法 | `add(e)` | `offer(e)` | `put(e)` | `offer(e,time,unit)` |
| 移除方法 | `remove()` | `poll()` | `take()` | `poll(time,unit)` |
| 检查方法 | `element()` | `peek()` | 不可用 | 不可用 |
Q4:**`JAVA`中的阻塞队列**
| 名称 | 含义 |
| ----------------------- | ------------------------------------------------------------ |
| `ArrayBlockingQueue` | 由**数组**结构组成的**有界**阻塞队列(最常用) |
| `LinkedBlockingQueue` | 由**链表**结构组成的**有界**阻塞队列(最常用)注意:一定要指定大小 |
| `PriorityBlockingQueue` | 支持**优先级排序**的**无界**阻塞队列。默认自然升序**排列** |
| `DelayQueue` | 支持**延时**获取元素的无界阻塞队列。 |
| `SynchronousQueue` | **不存储**元素的阻塞队列(可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程) |
| `LinkedTransferQueue` | 由**链表**结构组成的**无界**阻塞队列 |
| `LinkedBlockingDeque` | 由**链表**结构组成的**双向阻塞**队列(双向队列指的是可以从队列的两端插入和移出元素) |
![JAVA中的阻塞队列](https://cdn.jsdelivr.net/gh/LoveLifeEveryday/FigureBed@master/typora202003/08/202856-871463.png)
Q5:**实现原理:**
- 底层利用了`ReentrantLock`&`Condition`来实现自动加锁和解锁的功能
- 如果想详细了解阻塞队列实现原理的源码,笔者推荐一篇文章:[Android并发学习之阻塞队列](https://juejin.im/post/5b21ded3e51d4506bb3a84b6)
### 2.4 `Android`中的线程形态
#### 2.4.1 `AsyncTask`
Q1:**定义**:一种轻量级的**异步**任务类
>在`Android`中实现异步任务机制有两种方式:[Handler](https://juejin.im/post/5e61bf2de51d4526ea7f00bd)和`AsyncTask`
>
>- `Handler`机制存在的**问题**:代码相对臃肿;多任务同时执行时不易精确控制线程。
>- 引入`AsyncTask`的**好处**:创建异步任务更简单,直接继承它可方便实现后台异步任务的执行和进度的回调更新UI,而无需编写任务线程和`Handler`实例就能完成相同的任务。
Q2:**五个核心方法:**
| 方法 | 运行线程 | 调用时刻 | 作用 |
| -------------------- | -------- | ------------------------------------------------ | ------------------------------------------------------------ |
| `onPreExecute()` | 主线程 | 在异步任务执行之前被调用 | 可用于进行一些界面上的**初始化**操作 |
| `doInBackground()` | 子线程 | 异步任务执行时 | 可用于处理所有的**耗时任务**。若需要更新`UI`需调用 `publishProgress()` |
| `onProgressUpdate()` | 主线程 | 调用`publishProgress()`之后 | 可利用方法中携带的参数如`Progress`来对`UI`进行相应地更新 |
| `onPostExecute()` | 主线程 | 在异步任务执行完毕并通过`return`语句返回时被调用 | 可利用方法中返回的数据来进行一些**UI**操作 |
| `onCancelled()` | 主线程 | 当异步任务被取消时被调用 | 可用于做**界面取消**的更新 |
> 注意:
>
> - 不要直接调用上述方法
> - `AsyncTask`对象必须在**主线程**创建
Q3:**开始和结束异步任务的方法**
- `execute()`
> - 必须在**主线程**中调用
> - 作用:表示开始一个异步任务
> - 注意:一个异步对象只能调用一次`execute()`方法
- `cancel()`
> - 必须在**主线程**中调用
> - 作用:表示停止一个异步任务
Q4:工作原理:
- 内部有一个静态的`Handler`对象即`InternalHandler`
> - 作用:将执行环境从线程池切换到主线程;通过它来发送任务执行的进度以及执行结束等消息
>
> - 注意:必须在主线程中创建
- 内部有两个线程池:
> - `SerialExecutor`:用于任务的排队,默认是**串行**的线程池
> - `THREAD_POOL_EXECUTOR`:用于真正执行任务
- 排队执行过程:
> - 把参数`Params`封装为`FutureTask`对象,相当于`Runnable`
> - 调用`SerialExecutor.execute()`将`FutureTask`插入到任务队列`tasks`
> - 若没有正在活动的`AsyncTask`任务,则就会执行下一个`AsyncTask`任务。执行完毕后会继续执行其他任务直到所有任务都完成。即默认使用**串行**方式执行任务。
执行流程图:
![AsyncTask工作原理](https://s2.ax1x.com/2020/03/08/3zgr8A.png)
**注意**:`AsyncTask`不适用于进行特别耗时的后台任务,而是建议用线程池
> 如果想要了解具体源码的读者,笔者推荐一篇文章:[Android AsyncTask完全解析,带你从源码的角度彻底理解](https://blog.csdn.net/guolin_blog/articlehttps://img.qb5200.com/download-x/details/11711405)
#### 2.4.2 `HandlerThread`
Q1:定义:
`HandlerThread`是一个线程类,它继承自`Thread`
> 与普通`Thread`的区别:具有**消息循环**的效果。原理:
>
> - 内部`HandlerThread.run()`方法中有`Looper`,通过`Looper.prepare()`来创建消息队列,并通过`Looper.loop()`来开启消息循环
Q2:实现方法
- 实例化一个`HandlerThread`对象,参数是该线程的名称
- 通过 `HandlerThread.start()`开启线程
- 实例化一个`Handler`并传入`HandlerThread`中的`Looper`对象,使得与`HandlerThread`绑定
- 利用`Handler`即可执行异步任务
- 当不需要`HandlerThread`时,通过`HandlerThread.quit()`/`quitSafely()`方法来终止线程的执行
```java
private HandlerThread myHandlerThread ;
private Handler handler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//实例化HandlerThread
myHandlerThread = new HandlerThread("myHandler") ;
//开启HandlerThread
myHandlerThread.start();
//将Handler对象与HandlerThread线程绑定
handler =new Handler(myHandlerThread.getLooper()){
@Override
publicvoid handleMessage(Message msg) {
super.handleMessage(msg);
// 这里接收Handler发来的消息,运行在handler_thread线程中
//TODO...
}
};
//在主线程给Handler发送消息
handler.sendEmptyMessage(1) ;
new Thread(new Runnable() {
@Override
publicvoid run() {
//在子线程给Handler发送数据
handler.sendEmptyMessage(2) ;
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
//终止HandlerThread运行
myHandlerThread.quit() ;
}
```
Q3:**用途**
- 进行**串行**异步通信
- 构造`IntentService`
- 方便实现在子线程(工作线程)中使用`Handler`
Q4:原理:
- 实际就是`HandlerThread.run()`里面封装了`Looper.prepare()`和`Looper.loop()`,以便能在子线程中使用`Handler`
- 同时,`HandlerThread.getLooper()`中使用了`wait()和synchronized代码块`,当`Looper==NULL`的时候,锁住了当前的对象,那什么时候唤醒等待呢?当然是在初始化完该线程关联`Looper`对象的地方,也就是`run()`
> 想了解源码的话,笔者推荐一篇文章:[浅析HandlerThread](https://blog.csdn.net/ta893115871/articlehttps://img.qb5200.com/download-x/details/55272187)
#### 2.4.3 `IntentService`
Q1:定义:
`IntentService`是一个继承自`Service`的抽象类
Q2:优点:
- 相比于线程:由于是服务,优先级比线程高,更不容易被系统杀死。因此较适合执行一些**高优先级**的后台任务
- 相比于普通`Service`:可**自动创建**子线程来执行任务,且任务执行完毕后**自动退出**
Q3:使用方法
- 新建类并继承`IntentService`,重写`onHandleIntent()`,该方法:
> - 运行在子线程,因此可以进行一些耗时操作
> - 作用:从`Intent`参数中区分具体的任务并执行这些任务
- 在配置文件中进行注册
- 在活动中利用`Intent`实现`IntentService`的启动:
```java
Intent intent = new Intent(this, MyService.class);
intent.putExtra("xxx",xxx);
startService(intent);//启动服务
```
> 注意:无需手动停止服务,`onHandleIntent()`执行结束之后,`IntentService`会自动停止。
Q4:工作原理
- 在`IntentService.onCreate()`里创建一个`Thread`对象即`HandlerThread`,利用其内部的`Looper`会实例化一个`ServiceHandler`
- 任务请求的`Intent`会被封装到`Message`并通过`ServiceHandler`发送给`Looper`的`MessageQueue`,最终在`HandlerThread`中执行
- 在`ServiceHandler.handleMessage()`中会调用`IntentService.onHandleIntent()`,可在该方法中处理后台任务的逻辑,执行完毕后会调用`stopSelf()`,以实现自动停止
![总体流程图](https://cdn.jsdelivr.net/gh/LoveLifeEveryday/FigureBed@master/typora202003/09/130332-40654.png)
下面继续来研究下:将`Intent` 传递给服务 & 依次插入到工作队列中的流程
![Intent传递流程](https://s2.ax1x.com/2020/03/09/8Szv4g.png)
> 如果对`IntentService`的具体源码感兴趣的话,笔者推荐一篇文章:[Android多线程:IntentService用法&源码分析](https://blog.csdn.net/carson_ho/articlehttps://img.qb5200.com/download-x/details/53407806)
### 2.5 线程池
Q1:**优点**
- **重用**线程池中的线程,避免线程的创建和销毁带来的性能消耗
- 有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致阻塞现象
- 进行**线程管理**,提供定时/循环间隔执行等功能
Q2:**构造方法分析**
> - 线程池的概念来源:Java中的`Executor`,它是一个接口
> - 线程池的真正实现:`ThreadPoolExecutor`,提供一系列参数来配置线程池
```java
//构造参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
```
- `corePoolSize`:核心线程数
> - 默认情况下,核心线程会在线程中一直存活
>
> - 当设置`ThreadPoolExecutor`的`allowCoreThreadTimeOut`属性为
>
> A.`true`:表示核心线程闲置超过超时时长,会被回收
>
> B.`false`: 表示核心线程不会被回收,会在线程池中一直存活
- `maximumPoolSize`:最大线程数
> 当活动线程数达到这个数值后,后续的任务将会被阻塞
- `keepAliveTime`:非核心线程超时时间
> - 超过这个时长,闲置的非核心线程就会被回收
> - 当设置`ThreadPoolExecutor`的`allowCoreThreadTimeTout`属性为`true`时,`keepAliveTime`对核心线程同样有效
- `unit`:用于指定`keepAliveTime`参数的时间单位
> 单位有:`TimeUnit.MILLISECONDS`、`TimeUnit.SECONDS`、`TimeUnit.MINUTES`等;
- `workQueue`:任务队列
> 通过线程池的`execute()`方法提交的`Runnable`对象会存储在这个参数中
- `threadFactory`:线程工厂,可创建新线程
> 一个接口,只有一个方法`Thread newThread(Runnable r)`
- `handler`:在线程池无法执行新任务时进行调度
Q3:**`ThreadPoolExecutor`的默认工作策略**
![处理流程](https://i.bmp.ovh/imgs/2019/11/c61f6297cfff9f18.png)
Q4:**线程池的分类**
| 名称 | 含义 | 特点 |
| ---------------------- | ------------------------------------------------------------ | ---------------------------------- |
| `FixThreadPool` | 线程数量固定的线程池,所有线程都是**核心线程**,当线程空闲时**不会**被回收 | 能**快速**响应外界请求 |
| `CachedThreadPool` | 线程数量不定的线程池(最大线程数为**Integer.MAX_VALUE**),只有**非核心线程**,空闲线程有超时机制,超时回收 | 适合于执行大量的**耗时较少**的任务 |
| `ScheduledThreadPool` | 核心线程数量**固定**,非核心线程数量**不定** | **定时**任务和**固定**周期的任务 |
| `SingleThreadExecutor` | 只有**一个核心线程**,可确保所有的任务都在同一个线程中**按顺序**执行 | 无需处理**线程同步**问题 |
## 三.再聊聊`AsyTask`的不足
> `AsyncTask` 看似十分美好,但实际上存在着非常多的**不足**,这些不足使得它逐渐退出了历史舞台,因此如今已经被 `RxJava`、`协程`等新兴框架所取代(PS:有机会希望能和大家一起探究下`RxJava`的源码)
- 生命周期
> `AsyncTask` 没有与 `Activity`、`Fragment` 的生命周期绑定,即使 `Activity` 被销毁,它的 `doInBackground` 任务仍然会继续执行
- 取消任务
> `AsyncTask` 的 `cancel` 方法的参数 `mayInterruptIfRunning` 存在的意义不大,并且它无法保证任务一定能取消,只能尽快让任务取消(比如如果正在进行一些无法打断的操作时,任务就仍然会运行)
- 内存泄漏
>- 由于它没有与 `Activity` 等生命周期进行绑定,因此它的生命周期仍然可能比 `Activity` 长
>- 如果将它作为 `Activity` 的非 `static` 内部类,则它会持有 `Activity` 的引用,导致 `Activity` 的内存无法释放。(PS:与 `Handler`的内存泄漏问题类似,参考文章:[进阶之路 | 奇妙的Handler之旅](https://juejin.im/post/5e61bf2de51d4526ea7f00bd))
- 并行/串行
> 由于 `AsyncTask` 的串行和并行执行在多个版本上都进行了修改,所以当多个 `AsyncTask` 依次执行时,它究竟是串行还是并行执行取决于用户手机的版本。具体修改如下:
>
> A.`Android 1.6` 之前:各个 `AsyncTask` 按串行的顺序进行执行
>
> B.`Android 3.0` 之前:由于设计者认为串行执行效率太低,因此改为了并行执行,最多五个 `AsyncTask` 同时执行
>
> C.`Android 3.0` 之后:由于之前的改动,很多应用出现了并发问题,因此引入 `SerialExecutor` 改回了串行执行,但对并行执行进行了支持
------
如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力
本文参考链接:
- 《Android 开发艺术探索》
- 《Android 进阶之光》
- [进阶之路 | 奇妙的Handler之旅](https://juejin.im/post/5e61bf2de51d4526ea7f00bd)
- [Java线程中断的正确姿势](https://www.jianshu.com/p/264d4e1b76af)
- [面试官最爱的volatile关键字](https://juejin.im/post/5a2b53b7f265da432a7b821c#heading-2)
- [究竟什么是可重入锁?](https://blog.csdn.net/rickiyeat/articlehttps://img.qb5200.com/download-x/details/78314451)
- [Android并发学习之阻塞队列](https://juejin.im/post/5b21ded3e51d4506bb3a84b6)
- [Android AsyncTask完全解析,带你从源码的角度彻底理解](https://blog.csdn.net/guolin_blog/articlehttps://img.qb5200.com/download-x/details/11711405)
- [浅析HandlerThread](https://blog.csdn.net/ta893115871/articlehttps://img.qb5200.com/download-x/details/55272187)
- [Android多线程:IntentService用法&源码分析](https://blog.csdn.net/carson_ho/articlehttps://img.qb5200.com/download-x/details/53407806)
- [要点提炼|开发艺术之线程](https://www.jianshu.com/p/ab77a2e83c52)
- [AsyncTask 源码解析:Android 自带的异步任务工具](https://juejin.im/post/5e0dcae06fb9a047ef326bf1#heading-5)
加载全部内容