前言
线程在使用过程中,往往会遇见直接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方案对线程进行监控,并打印线程堆栈信息。
网友评论