Android线程优化

348 阅读4分钟

线程调度原理

  • 任意时刻 ,只有一个线程占用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()));
            }
        });

参考: