亲宝软件园·资讯

展开

进阶之路 | 奇妙的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)

加载全部内容

相关教程
猜你喜欢
用户评论