我正在参与「启航方案」
1、 前语
当咱们在使用程序中启动一个线程的时分,也是有可能发生OOM错误的。当咱们看到以下log的时分,就说明体系分配线程栈失败了。
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Out of memory
这种状况可能是两种原因导致的。
- 第一个便是体系的内存不足的时分,咱们去启动一个线程。
- 第二种便是进程内运转的线程总数超过了体系的约束。
如果是内存不足的状况,需按照堆内存管理的方法来进行处理,检查使用内存泄漏问题并优化,此状况不作为本次讨论的要点。
本次主要讨论进程内运转的线程总数超过了体系的约束所导致的状况。出现此状况时,咱们就需要经过操控并发的线程总数来处理这个问题。
想要操控并发的线程数。最直接的一种方法便是使用回收的思路,也便是让咱们的线程经过串行的方法来履行;一个线程履行完毕之后,再启动下一个线程。这样就可以让并发的线程总数达到一个可控的状况。
另外一种方法便是经过复用来处理,让同一个线程的实例可以被重复的使用,只创立较少的线程实例,就能完结很多的异步操作。
2、异步使命的方法比照
比照一下,在安卓平台咱们比较常用的敞开异步使命的方法中,有哪些是更加有利于咱们进行线程总数的操控的。
敞开异步使命的方法 | 特色 |
---|---|
Thread.start() | 并行,难以办理 |
HandlerThread | 带音讯循环的线程,线程内部串行使命(线程复用) |
AsyncTask | 轻量级,串行(3.0以上),可以结合线程池使用 |
线程池 | 可办理并发数,池化复用线程 |
Kotlin协程 | 简化异步编程代码,复用线程,进步并发效率 |
##### 2.1 Thread |
从最简单的直接创立Thread的实例的方法来说起。在Java中这种方法虽然是最简单的去敞开一个线程的方法,可是在实践开发中,一旦咱们经过这种方法去自己创立 Thread 类的实例,而且调用 start 来敞开一个线程的话,所敞开的线程会十分的难以调度和办理。这种线程也便是咱们平时所说的野线程。所以咱们最好不要直接的创立thread类的实例。
2.2 HandlerThread
public class HandlerThread extends Thread { }
HandlerThread是Thread类的子类,对Thread做了很多便当的封装。它有自己的Loop,它可以进行音讯循环,所以就可以做到经过Handler履行异步使命,也可以做到在不同的线程之间,经过Handler进行现成的通讯。咱们可以使用Handler的post操作,让咱们在一个线程内部串行的履行多个异步使命。从内存的视点来说,也就相当于对线程进行了复用。
2.3 AsyncTask
AsyncTask是一个相对更加轻量级,专门为了完结履行异步使命,然后回来UI线程更新UI的操作而规划的。关于咱们来说,AsyncTask更像是一个使命的概念,而不是一个线程的概念。咱们不需要把它作为一个线程去理解。 AsyncTask的实质,其实也是对线程和Handler的封装。
- Android 1.6前,串行履行,原理:一个子线程进行使命的串行履行;
- Android 1.6到2.3,并行履行,原理:一个线程数为5的线程池并行履行,但如果前五个使命履行时间过长,会阻塞后续使命履行,故不适合很多使命并发履行;
- Android 3.0后,串行履行,原理:大局线程池进行串行处理使命;
到了Android 3.0以上版本,默认是串行履行的,可是可以结合线程值来实现有约束的并行。也可以达到一个约束线程总数的意图。
2.4 线程池
Java言语自身也为咱们提供了线程池。线程池的作用便是可以办理并发数,而且可以持续的去复用线程。如果在一个使用内部的全部异步操作,全部都选用线程池的方法来敞开的话,那么咱们就可以办理咱们一切的异步使命了。这样一来,可以大大的下降线程管理的本钱。
2.5 Kotlin协程
在Kotlin中还引入了协程的概念。协程给传统的Java的异步编程带来最大的改动,便是可以让咱们更加优雅的去实现异步使命。咱们前面所说的这几种异步使命的履行方法,都需要咱们额外的去写很多的样本代码。而Kotlin协程就可以做到让咱们用写同步代码的方法去写异步代码。
在语法的层面上,协程的另一个优势便是性能方面。协程可以帮助咱们用更少的线程去履行更多的并发使命。相同也下降了咱们管理内存的本钱。从管理内存的视点来说,用线程池接收线程或者选用协程都是很好的方法。
参考
heapdump.cn/article/376…
/post/685003…
developer.umeng.com/docs/193624…