关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。
未经允许不得转载
目录
- 一、导读
- 二、概览
- 2.1、线程的属性
- 三、线程的调度
- 3.1 Java内存模型
- 3.2 高速缓存
- 3.3 Java 线程调度机制
- 3.4 Android线程调度
- 四、 推荐阅读
一、导读
我们继续总结学习基础知识,温故知新。
我们在前面讲过
【线程相关基础知识】,本文在此基础上,讲述Android线程调度的相关知识。
二、概览
线程优化是性能优化的一个重点,使用不当的话也会有很大的影响,所以我们需要清楚线程调度原理。
通过基础知识,我们知道任意时刻,只有一个线程占用CPU,处于运行状态,其他的现场处于等待状态,即使是多线程,也是如此,区别就
在于多线程并发是多个线程轮流获取CPU使用权(CPU时间片轮转机制)。
2.1、线程的属性
在日志中,我们经常可以看到这样的信息 com.xxx#RenderThread(119111), 这里面就有线程的名字及编号(id)。
线程有id、名字、类别以及优先级四个属性,我们分别列一下:
-
编号
线程的编号(id)用于标识不同的线程,每条线程拥有不同的编号;
这个id不能作为线程唯一标识,某个编号的线程运行结束后,该编号可能被后续创建的线程使用,因此编号不适合用作唯一标识,编号是只读属性,不能修改; -
名字
每个线程都有自己的名字(name),名字的默认值是 Thread-线程编号,比如 Thread-0 ;
除了默认值,我们也可以给线程设置名字,以我们自己的方式去区分每一条线程;
作用:给线程设置名字可以让我们在某条线程出现问题时,用该线程的名字快速定位出问题的地方 -
类别
线程的类别(daemon)分为守护线程和用户线程,我们可以通过 setDaemon(true) 把线程设置为守护线程; -
优先级
线程的优先级(Priority)用于表示应用希望优先运行哪个线程,线程调度器会根据这个值来决定优先运行哪个线程;
具体可参考【线程相关基础知识】
三、线程的调度
我们先来了解一些相关概念
3.1 Java内存模型
【jvm 堆、栈、方法区 & java 内存模型】
在多线程场景下,CPU会出现缓存一致性问题,处理器重新排序问题,
为了解决这个问题,制定了计算机内存模型。(原子性、可见性、有序性)
即是Java语言对这个操作规范的遵循,
JMM规定了所有的变量都存储在主存中,每个线程都有自己的工作区,
线程将使用到的变量从主存中复制一份到自己的工作区,线程对变量的所有操作(读取、赋值等)都必须在工作区,
不同的线程也无法直接访问对方工作区,线程之间的消息传递都需要通过主存来完成。
可以把这里主存类比成计算机内存模型中的主存,工作区类比成计算机内存模型中的高速缓存。
3.2 高速缓存
处理器的处理能力要远胜于主内存(DRAM)的访问速率,;
为了弥补处理器与主内存之间的差距,硬件设计者在主内存与处理器之间加入了高速缓存(Cache);
CPU 高速缓存是内置于 CPU(中央处理器)或位于处理器芯片上的小型快速内存区域。CPU 高速缓存存储主内存中经常使用的数据和指令,以减少 CPU 为这些信息访问主内存的次数
高速缓存相当于是一个由硬件实现的容量极小的散列表,这个散列表的 key 是一个对象的内存地址,value 可以是内存数据的副本,也可以是准备写入内存的数据;
3.3 Java 线程调度机制
1、 在任意时刻,CPU 只能执行一条机器指令,每个线程只有获取到 CPU 的使用权后,才可以执行指令;
也就是在任意时刻,只有一个线程占用 CPU,处于运行的状态;
2、 多线程并发运行实际上是指多个线程轮流获取 CPU 使用权,分别执行各自的任务;
3、 线程的调度由 JVM 负责,线程的调度是按照特定的机制为多个线程分配 CPU 的使用权;
线程调度模型分为两类:分时调度模型和抢占式调度模型;
-
分时调度模型
分时调度模型是让所有线程轮流获取 CPU 使用权,并且平均分配每个线程占用 CPU 的时间片; -
抢占式调度模型
让优先级高的线程占用 CPU,如果线程的优先级都一样,那就随机选择一个线程,并让该线程占用 CPU;
也就是如果我们同时启动多个线程,并不能保证它们能轮流获取到均等的时间片;
如果我们的程序想干预线程的调度过程,最简单的办法就是给每个线程设定一个优先级;
多线程是不安全的,具体参考线程的原子性、可见性、有序性及线程安全
3.4 Android线程调度
Android是基于java开发的,但是又有所改动,线程调度也是有所区别的。
Android线程调度有两个决定因素:
- Android应用程序线程优先级
定义在android.os.Process类中,跟java线程优先级一样,值越小优先级越高,
/*** Standard priority of application threads.* Use with {@link #setThreadPriority(int)} and* {@link #setThreadPriority(int, int)}, <b>not</b> with the normal* {@link java.lang.Thread} class.*/public static final int THREAD_PRIORITY_DEFAULT = 0;/*** Standard priority background threads. This gives your thread a slightly* lower than normal priority, so that it will have less chance of impacting* the responsiveness of the user interface.* Use with {@link #setThreadPriority(int)} and* {@link #setThreadPriority(int, int)}, <b>not</b> with the normal* {@link java.lang.Thread} class.*/public static final int THREAD_PRIORITY_BACKGROUND = 10;
我们可以在代码中直接定义线程的优先级,如:
thread.setPriority(android.os.Process.THREAD_PRIORITY_DEFAULT);
- cgroup
android中还定义了一种更严格的群组调度策略,了解linux的同学都清楚,
Linux的cgroup(Control Group)是一种内核特性,用于对进程组进行资源限制、优先级管理和统计等操作。
cgroup可以将一组相关的进程组织在一起,并对它们施加各种资源控制策略,以确保系统资源的有效分配和管理。
Android就是借用了这种特性的思想,我们简单理解为将进程进行了分组,比如后台进程组,将后台进程归为一个组,这个组里面的
线程在cpu忙的时候,只有比较小的概率能获取到cpu。
这种前台和后台分开执行策略,即允许后台线程执行任务,保证前台线程可以获取更多的CPU,不会对前台现场造成很大的影响,极大概率的减少卡顿。
下面的线程会移到后台group
- 优先级低的线程;
- 不在前台运行的线程
总结一下:
1、线程不是越多越好,太多反而影响效率,大家可参考一下:
线程切换开销
2、另外在设置优先级的时候要酌情一下,并不是都设置最高,最好根据业务情况及工作量来。
3、现场具有继承性,如A线程中启动B线程,则B线程的优先级跟A一样。
四、 推荐阅读
Java 专栏
SQL 专栏
数据结构与算法
Android学习专栏
未经允许不得转载