Android性能优化之线程优化

前言

 线程在使用过程中,往往会遇见直接new Thread()的操作,这样做除了方便外,坏处也有很多,例如线程无法复用、线程数无法控制导致CPU频繁切换降低性能等问题。我们应当在项目早期就有意识的使用线程池收敛线程,降低后期的工作量。


1 线程调度

1.1 线程调度原理

  • 任意时刻,只有一个线程占用CPU,处于运行状态
  • 多线程并发:多个线程轮流获取CPU使用权
  • JVM负责线程调度:按照特定机制分配CPU使用权

1.2 线程调度模型

  • 分时调度模型:轮流获取、均分CPU时间
  • 抢占式调度模型:优先级高获取使用权,如果优先级都一样就随机选择线程执行。JVM使用此模型

1.3 Android线程调度

 android为线程调度分别提供了设置nice值、cgroup两种方法。

  • nice值
    Process中定义,值越小优先级越高。默认优先级是Process.THREAD_PRIORITY_DEFAULT
    Android线程优先级
  • cgroup
    为了避免前、后台线程过多时相互影响对方执行,Android采取更严格的群组调度策略,保证前台线程可以获取到更多的CPU,但是又不会严重影响到后台线程的计算。
  • 什么线程会被分配到后台线程?
    优先级较低的线程、不在前台使用的线程会被移动到后台线程。
  • nice值和cgroup如何使用?
    nice值和cgroup两个值,我们只能设置nice值
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)

-注意点
①线程过多会导致CPU频繁切换,降低线程运行效率。
②任务量大的线程应为较低优先级,避免产生①问题
③优先级具有继承性


2 Android异步

 除了可以使用线程池外,Android还为我们提供了三种异步方法:HandlerThread、IntentService、AsyncTask。

2.1 Thread

 最简单粗暴的方式,不建议直接使用,因为存在以下几点问题:
①不易复用,频繁创建及销毁开销大
②复杂场景不易使用,例如定时任务

2.2 HandlerThread

  • 自带消息循环
    ①串行执行
    ②长时间运行,不断从队列中获取任务

2.3 IntentService

  • 继承自Service在内部创建HnadlerThread
    ①异步,不占用主线程
    ②优先级较高,不易被系统kill

2.4 AsyncTask

  • Android提供异步工具类
    ①无需自己处理线程切换
    ②注意版本不一致

2.5 线程池

  • Java提供的线程池
    ①易复用,减少频繁创建、销毁的时间
    ②功能强大:定时、任务队列、并发数控制等

2.6 RxJava

  • 由强大的Scheduler集合提供
    ①不同类型的区分:IO、Computation

3 Android线程优化

3.1 使用准则

  • 严禁直接new Thread
  • 提供基础线程池供各个业务线使用
    ①避免各个业务线各自维护一套线程池,导致线程数过多
  • 根据任务类型选择合适的异步方式
    ①优先级低,长时间执行可以使用HandlerThread
  • 创建线程必须命名
    ①方便定位线程归属
    ②运行期Thread.currentThread().setName修改名字。因为重命名容易被忽略,需要注意
  • 关键异步任务监控
    ①异步不等于耗时
    ②AOP监控
  • 重视优先级设置
    ①Process.setThreadPriority

3.2 线程池工具类

 项目走向成熟期之后需要对线程进行收敛,避免线程随意创建,如果集成的框架能主动设置线程池,我们应当设置成自己的线程池,对线程创建及使用情况进行监控。

public class ThreadPoolUtils {
    private final Executor mDiskExecutor;

    private final Executor mNetworkExecutor;

    private final Executor mMainThread;
    private final ScheduledThreadPoolExecutor schedule;
    //cpu密集型任务最大线程数
    private int CPU_Runtime = Runtime.getRuntime().availableProcessors() + 1;
    //io密集型任务最大线程数
    private int IO_Runtime = Runtime.getRuntime().availableProcessors() * 2 + 1;

    private static ThreadPoolUtils instance;

    private static final Object object = new Object();

    public static ThreadPoolUtils getInstance() {
        if (instance == null) {
            synchronized (object) {
                if (instance == null) {
                    instance = new ThreadPoolUtils();
                }
            }
        }
        return instance;
    }

    private ThreadPoolUtils() {

        this.mDiskExecutor = Executors.newFixedThreadPool(CPU_Runtime, new MyThreadFactory("MonsterDE"));

        this.mNetworkExecutor = Executors.newFixedThreadPool(IO_Runtime, new MyThreadFactory("MonsterNE"));

        this.mMainThread = new MainThreadExecutor();

        this.schedule = new ScheduledThreadPoolExecutor(IO_Runtime, new MyThreadFactory("MonsterSC"),
                new ThreadPoolExecutor.AbortPolicy());
    }

     /**
     * 记录是线程池中第几个线程
     */
    private static class MyThreadFactory implements ThreadFactory {

        private final String name;
        private final AtomicInteger mCount = new AtomicInteger(1);

        MyThreadFactory(String name) {
            this.name = name;
        }

        @Override
        public Thread newThread(@NonNull Runnable r) {
            return new Thread(r, name + "-" + mCount.getAndIncrement() + "-");
        }
    }

    public Executor diskIO() {
        return mDiskExecutor;
    }

    public ScheduledThreadPoolExecutor schedule() {
        return schedule;
    }

    public Executor networkIO() {
        return mNetworkExecutor;
    }

    public Executor mainThread() {
        return mMainThread;
    }

    private static class MainThreadExecutor implements Executor {
        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command) {
            mainThreadHandler.post(command);
        }
    }
}


4 锁定线程

  • 为什么要锁定线程?
    项目变大之后需要收敛线程,因为我们自身的项目源码、第三方库、aar文件都有可能存在线程的创建,我们需要通过监控预防去预防项目往不好的地方发展。
  • 锁定线程创建方案
    因为AAR可能混淆过,无法通过在new Thread中使用,但是我们可以在线下使用ARTHook方法,在线程构造函数打印调用堆栈。

总结

 线程优化可以分为以下几点:

  • 修改线程优先级(任务量大的可以降低优先级;但是一般不建议修改优先级,因为都想着自己线程的优先级更高的时候,线程优先级设置会变得无意义,深层次的原因是映射到系统线程等级问题,这里就不再展开说明)
  • 选择合适的异步方法,例如Android提供的三种异步方法以及线程池,开发过程中可根据实际情况选择
  • 使用线程池进行线程收敛,避免直接new Thread()
  • 锁定线程,可通过ARTHook方案对线程进行监控,并打印线程堆栈信息。

你可能感兴趣的:(Android性能优化之线程优化)