线程调度原理
- 任意时刻 ,只有一个线程占用CPU,处于运行状态
- 多线程并发 :轮流获取CPU使用权
- JVM负责线程调度:按照特定机制分配CPU使用权
调度模型
- 分时调度模型:轮流获取,均分cpu时间
- 抢占调度模型 :优先级高的获取,jvm使用这种模型
Android 线程调度
- 高Nice Value对应较低的线程优先级,意味着更少的执行机会,让步于高优先级的UI线程;
- Cgroups可以更好的凸显某类线程的优先级,Android中有两类group尤其重要:一类是default group,对应UI线程。另一类是background group,对应工作线程;
- 进程的属性变化也会影响到线程的调度,当一个App进入后台,该App所属的整个线程都将进入background group,以确保处于foreground、用户可见的进程能获取到尽可能多的CPU资源。
正确使用异步
Thread
- 优先级与UI线程一致,抢占资源处于同一起跑线;
- 匿名内部类默认持有外部类的引用,有内存泄漏的风险;
HandlerThread
优点:
- 串行执行,没有并发带来的问题;
- 不退出的前提下一直存在,避免线程相关的对象频繁重建和销毁造成的资源消耗 缺点:
- 不指定优先级的情景下默认优先级为THREAD_PRIORITY_DEFAULT,与UI线程同级别。
适用场景:
- HandlerThread适合串行处理多任务的场景;
IntentService
- 同HandlerThread的优势;
- 开启服务,进程优先级会提升;
- 无需手动关闭,执行完之后自动结束。 适用场景:
- IntentService适合处理与UI无关的多任务场景;
ThreadPoolExecutor
优势:
- 线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销;
- 线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量;
- 在执行大量异步任务时提高了性能;
- Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等。
注意事项:
- 使用线程池需要特别注意同时并发线程数量的控制。因为CPU只能同时执行固定数量的线程数,一旦同时并发的线程数量超过CPU能够同时执行的阈值,CPU就需要花费精力来判断到底哪些线程的优先级比较高,在不同的线程之间进行调度切换。一旦同时并发的线程数量达到一定的量级,CPU在不同线程之间进行调度的时间就可能过长,反而导致性能严重下降;
- 每开一个新的线程,都会耗费至少64K以上的内存。线程池中存在了过多的并发数量不仅会影响CPU的调度时间而且会减少可用内存;
- 线程的优先级具有继承性,在某线程中创建的线程会继承此线程的优先级。那么我们在UI线程中创建了线程池,其中的线程优先级是和UI线程优先级一样的;所以仍然可能出现20个同样优先级的线程平等的和UI线程抢占资源。
统一线程库
- IO密集型任务不消耗CPU,核心池可以很大。
- CPU密集型任务:核心池大小和CPU核心数相关。
如何锁定线程创建者
- 获取线程的位置获取堆栈
- 所有异步的方式,都会走到new Thread 所以我们 Hook Thread的构造,获取堆栈信息查看线程是谁创建的
DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Thread thread = (Thread) param.thisObject;
LogUtils.i(thread.getName()+" stack "+Log.getStackTraceString(new Throwable()));
}
});