上一章咱们具体介绍了线程池,经过合理地运用线程池,能有效发挥 CPU 的利用率,提高运用速度。此外,咱们还有许多方式能够用来提高 CPU 的利用率。这一章咱们就接着来介绍两种计划:充分利用 CPU 的搁置时刻,和削减 CPU 的等候。

下面,咱们先来看假如充分利用 CPU 的搁置时刻。

充分利用 CPU 搁置时刻

除了游戏类视频类运用,很少有运用会持续以较高的状况耗费 CPU,而且大部分状况下,CPU 都或许处于搁置状况。假如咱们能充分利用 CPU的 搁置时刻,把中心场景运转时需求履行的使命或许数据放在搁置时去提早预加载,那在翻开这些场景时,CPU 需求履行的指令数就会削减,速度也就会有提高。

为什么预加载使命要放在 CPU 的搁置时刻呢?假如预加载使命不是放在 CPU 的搁置时刻就会和中心场景抢占资源,导致中心场景速度变慢。比方,咱们常常会在发动时预加载一些逻辑以此来提高后边场景的速度,但这样会导致发动变慢。假如把这些使命放在 CPU 搁置后再履行,就能做到既不影响发动的速度,又能提高后边场景的速度了。

想要充分利用 CPU 的搁置时刻,首要需求知道 CPU 现已搁置了,想要做到这一点,咱们能够发动一个守时使命,每 5 秒检测一次 CPU 是否现已搁置,假如现已搁置了,则回调通知各个业务进行预加载使命的履行(5 秒不是固定值,需求依据所开发运用的类型来调整,比方是归于 CPU 密集型仍是核算密集型)。

计划中最要害的一步便是检测到 CPU 现已搁置,我在这儿介绍两种计划:

  1. 经过读取 proc 文件节点下的 CPU 数据判别 CPU 是否搁置;
  1. 经过 times 函数判别 cpu 是否搁置。

下面先从第 1 种讲起。

读取 proc 节点文件

咱们知道在 Linux 体系上,设备和运用的大部分信息和数据都会记录在 proc 目录下的某个文件中,比方前面反复说到的 maps 文件。CPU 相关的数据相同也能够在 proc 下的文件中获取,咱们能够经过读取 /proc/stat 节点获取 CPU 的总运转时刻,经过读取 /proc/运用进程id/stat 文件获取具体某个进程的 CPU 耗费时刻。那么咱们只需求读取在一段时刻内(如 5 秒)运用的 CPU 耗费时刻,然后除以 CPU 的总运转时刻,就能得出 CPU 占用率这个指标

假如 CPU 占用率是较低的,比方低于 30 % 的阈值,就以为 CPU 现已搁置了。当然,30% 这个值也不是固定的,关于一个 8 核的 CPU 来说,极限状况下也便是一切核都在为这个进程服务的时分,CPU 占用率能够挨近 800%,而一个 4 核手机,极限状况 CPU 占用率也只能挨近 400%,所以关于功能高的手机,这个阈值能够设置高一点,功能差的手机,这个阈值能够设置的低一点。

CPU 总运转时刻

下面带大家了解一下这两个文件的数据详情,首要是 /proc/stat 节点文件,它的数据如下:

adb shell cat /proc/stat
cpu  125008 117667 128037 3196237 16160 18733 11734 0 0 0
cpu0 25839 24942 30963 355685 4113 6000 2280 0 0 0
cpu1 23695 27365 27443 363280 2860 3407 2416 0 0 0
cpu2 14162 10115 20652 398174 1798 2782 2451 0 0 0
cpu3 12507 9615 21847 397652 2491 3437 2433 0 0 0
cpu4 10043 11091 5759 424106 867 783 324 0 0 0
cpu5 11832 9604 5749 423895 911 690 311 0 0 0
cpu6 14558 12965 7616 415413 1614 799 410 0 0 0
cpu7 12367 11967 8005 418027 1502 832 1104 0 0 0
intr 14033565 0 0 0 0 2212446 0 138913 137167 3850 0 197 7170 0 0 0 0 56433 39773 0 0 0 0 0 0 0 0 0 0 0 91 0 0 0 0 0 0 0 0 6 0 0 0 0 0 4 0 0 0 0 0 25593 16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 12555 2394 3855 689535 0 0 0 1378 561 11 11 0 0 0 0 0 0 0 0 0 0 0 23 326966 9 3546 746 8918 140 0 6 194 0 6 80952 84248 23864 0 0 0 0 0 47472 22665 233489 0 0 0 0 0 3635 0 6873 50 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 5 4 4 0 0 0 3193 0 1 0 2 0 2 0 2 2 0 0 2 136 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 163 0 0 0 4 0 0 0 0 0 0 0 0 0 919 39 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 212 0 0 0 2250 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 39992 18820 0 0 11 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 952 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 4 2 2 0 4 3 15 6 0 0 0 0 0 0 0 0 51 0 17 0 0 0 9 801 2 32 56 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 197212 0 0 4 0 0 0 0 0 0 0 0 0 0 0 26 0 0 17348 0 4 1 0 1 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 20963274
btime 1666009901
processes 25537
procs_running 1
procs_blocked 0
softirq 5378992 18820 1620970 6861 558158 660031 0 673436 869906 0 970810

面对上面一连串鳞次栉比的数字,你或许现已头疼了,但实践上这些数据比较好理解。接下来,咱们从上到下、从左到右解释一下这些数据。

前 8 行数据实践是 CPU 的每个核从体系发动到当时时刻,不同维度累积下的 CPU 时刻。

1. CPU 称号 2. user 3. nice 4. system 5. idle 6. io_wait 7. irp 8. soft_irp
从体系发动到当时时刻,用户态累积耗费的 CPU 时刻 从体系发动到当时时刻,nice 值为负的进程所累积耗费的 CPU 时刻 从体系发动到当时时刻,内核态累积耗费的 CPU 时刻 从体系发动到当时时刻,除 IO 等候时刻以外的其他等候时刻 从体系发动开端到当时时刻,累计 IO 等候时刻(这个时刻是不准确的,因为需求 IO 等候时,调度器会将 CPU 切换给其使命运用) 从体系发动到当时时刻,累计的硬中止时刻 从体系发动到当时时刻,累计的软中止时刻 保留字段,目前无意义
cpu(一切核累加) 125008 117667 128037 3196237 16160 18733 11734 0 0 0
cpu0(第一个核) …… …… …… …… …… …… …… 0 0 0
…… …… …… …… …… …… …… …… 0 0 0
cpu7(第八个核)) …… …… …… …… …… …… …… 0 0 0

接着便是 CPU 数据后边的数据

  1. intr:体系发动以来的一切 interrupts 次数
  1. ctxt: 体系发动以来累计上下文切换次数
  1. btime:体系发动时长
  1. processes:体系发动后所创立过的进程数量
  1. procs_running:处于运转状况的进程个数
  1. procs_blocked:处于等候 IO 的进程个数

实践上,关于 CPU 数据之后的数据,咱们不必太关怀,比方一大串的 intr 数据、ctxt 等,它们很少能用得上。有了上面的数据后,咱们只需求将第一行 CPU 数据中的 2 到 8 列数据累加起来 ,就能得到 CPU 的总运转时刻了。

进程耗费 CPU 时刻

有了 CPU 总运转时刻,还缺进程的 CPU 耗费时刻。所以咱们接着看”体系设置”这个运用的 /proc/pid/stat 节点文件,它的数据如下:

adb shell cat /proc/19700/stat
19700 (ndroid.settings) S 1271 1271 0 0 -1 1077952832 179904 0 28356 0 651 310 0 0 10 -10 42 0 529919 15416832000 25731 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 4 0 0 0 0 0 0 0 0 0 0 0 0 0

能够看到数据也有许多,总共有几十个,这儿从左到右介绍一下前 24 项数据(之后的数据就几乎不怎样用得到),里边不只包含了该进程所耗费的 CPU 数据,还包括许多其他的数据。下面是从左到右每个数据的意义

序号 数据 阐明
1 19700 pid: 进程 ID.
2 (ndroid.settings) 运用程序或指令的名字
3 S state:进程的状况。R: running;S:sleeping (TASK_INTERRUPTIBLE);D:disk sleep (TASK_UNINTERRUPTIBLE);T: stopped;T:tracing stop;Z:zombie;X:dead
4 1271 ppid: 父进程 ID
5 1271 pgid:进程组 ID
6 0 session:进程会话组 ID
7 0 tty_nr:当时进程的终端(Terminal)设备号
8 -1 tty_pgid:进程终端的前台进程号
9 1077952832 flags:进程标识位
10 179904 min_flt: 该进程不需求从硬盘拷数据而发生的缺页次数
11 0 cmin_flt:该进程累计的因等候子进程而发生的次缺页次数
12 28356 maj_flt:该进程需求从硬盘拷数据而发生的缺页次数
13 0 cmaj_flt:该进程累计的因等候子进程而发生的主缺页次数
14 651 utime: 该进程处于用户态的 CPU 时刻
15 310 stime: 该进程处于内核态的 CPU 时刻
16 0 cutime:当时进程等候子进程在用户态累计的 CPU 时刻
17 0 cstime: 当时进程等候子进程的在体系态累计的 CPU 时刻
18 10 priority: 进程优先级
19 -10 nice: 进程 nice 值
20 42 num_threads: 线程个数
21 0 itrealvalue: 已废弃字段
22 529919 starttime:进程发动总时长
23 15416832000 vsize:进程的虚拟内存大小,单位为 bytes
24 25731 rss: 进程独占内存+共享库,单位 pages,也便是 4 bytes

/proc/stat 和 /proc/pid/stat 文件中的数据许多,具体记录和 CPU 相关以及一些其他的数据,你并不需求记下来,当需求用到 CPU 相关的数据时,你再经过文档查询每个数据的意义就足够了。这儿具体解释各个字段的意义,也是为了便利你日后能用来查询。当咱们想要获取进程的 CPU 时刻,经过查询上表,发现第 13 项 utime 加上第 14 项 stime,便是这个进程总的 CPU 耗费时刻。

进程 CPU 运用率

有了上面的数据,接下来咱们就能够开端核算 CPU 的运用率了。CPU 运用率是限定在必定的时刻规模的,假如在这个时刻规模内,运用率低就阐明运用没怎样运用 CPU ,即处于搁置状况。这个时刻规模不宜太长,5 秒到 60 秒之间都能够。假如咱们预加载使命比较多,时刻能够缩短一些,比方 5 秒检测一次,在这 5 秒内假如 CPU 是搁置的,就履行预加载使命。但注意预加载使命需求打散,也便是每个搁置周期不能履行太多的预加载使命,防止一切预加载使命都一次履行而导致 CPU 过载。 那么怎样核算进程的 CPU 运用率呢?操作如下:

  1. 获取 CPU 总运转时刻以及进程 CPU 时刻:
long beforeCpuTime;
long beforeAppTime;
RandomAccessFile mProcStatFile;
RandomAccessFile mAppStatFile;
float getCpuUsage(){
    // 翻开/rpoc/stat和/proc/pid/stat文件
    if(mProcStatFile == null && mAppStatFile == null){
        mProcStatFile = RandomAccessFile("/proc/stat", "r");
        mAppStatFile = RandomAccessFile("/proc/" + android.os.Process.myPid() + "/stat", "r");
    }else{
        //假如文件现已翻开了,则将指针移到行头
        mProcStatFile.seek(0);
        mAppStatFile.seek(0);
    }
    // 分别读取两个文件的第一行
    String procStat = mProcStatFile.readLine();
    String appStat = mAppStatFile.readLine();
    // 依照空格拆分数据
    String[] procStats = procStat.split(" ");
    String[] appStats = appStat.split(" ");
    // 2,3,4,5,6,7,8项数据累加便是总 cpu 运转时刻
    long curCpuTime = Long.parseLong(procStats[2]) + Long.parseLong(procStats[3]) + Long.parseLong(procStats[4]) + Long.parseLong(procStats[5]) + Long.parseLong(procStats[6]) + Long.parseLong(procStats[7]) +Long.parseLong(rocStats[8]);
    // 13,14项数据相加便是进程 cpu 运转时刻
    long curAppTime = Long.parseLong(appStats[13]) + Long.parseLong(appStats[14]);
    // before值为零则是第一次获取
    if(beforeCpuTime == 0){
        return 0;
    }
    //核算cpu运用率
    float cpuUsage = caculateCpuUsage(curCpuTime,curAppTime,beforeCpuTime,beforeAppTime)
    beforeCpuTime = curCpuTime;
    beforeAppTime = curAppTime;
    return cpuUsate;  
}
  1. 每隔一段时刻片(这儿取 5 秒)获取一次数据,这儿就能够用调度线程池来履行周期使命了
mScheduledThreadPool.schedule(new Runnable() {
    @Override
    public void run() {
        //履行上面的逻辑
        float cpuUsage = getCpuUsage();
    }
}, 5, TimeUnit.SECONDS);
  1. 前后两次相减的进程 CPU 时刻除以前后两次相减的总 CPU 时刻,便是这 5 秒的 CPU 运用率了
float caculateCpuUsage(){
    return (beforeAppTime - curAppTime)/(float)(beforeCpuTime - curCpuTime);
}

这个时分,假如 cpuUsage 小于阈值,咱们就能够通知使命行列或许各个业务履行预加载使命啦。从代码中能够看到,读取 proc 节点需求读取并解析文件,假如咱们 5 秒读一次的话,对功能的损耗会比较高,所以为了下降功能损耗,咱们能够放在 Native 层经过 C++ 代码来读取和解析。当然,即使经过 C++ 来读取,也仍是对功能有必定的损耗,所以接下来给你介绍第二种方式:经过 Times 函数这一种低功能损耗的方式来判别 CPU 是否搁置。

Times 函数

经过文档能够看到,Times 函数能够直接回来用户的 CPU 时刻以及体系的 CPU 时刻,times 函数是体系函数,会直接从内核拿数据,所以不需求解析文件。

速度优化:CPU 优化(下)

因为 Times 函数是一个体系函数,咱们需求在 Native 层才干调用。所以咱们直接写一个 Native 方法,然后在 Java 层经过 Jni 履行这个 Native 方法,就能高效获取到进行所耗费的 CPU 时刻了。

#include <sys/times.h>
float getCpuTimes(JNIEnv *env) {
    struct tms currentTms;
    times(&currentTms);
    return currentTms.tms_utime + currentTms.tms_stime;
}

可是 Times 函数只能读取到运用耗费的 CPU 时刻,没法获取总的 CPU 时刻,咱们怎样核算 CPU 运用率来判别 CPU 是否处于搁置状况呢?实践上,咱们并不是必定需求 CPU 运用率才干判别 CPU 是否现已搁置,咱们还能够经过运用的 CPU 速率来判别 CPU 是否现已搁置。什么是 CPU 速率呢?CPU 速率 = 单位时刻内进程内耗费的 CPU 时刻 / 单位时刻。以 5 秒为例,则 CPU 速率的核算如下

//这儿app的cpu时刻都转化成了秒
float cpuSpeed =  (beforeAppTime - curAppTime) / 5f;

经过 Demo 的数据能够验证,当运用处于搁置状况时,CpuSpeed 必定在 0.1 以下,咱们能够依据运用的特性,经过经验值设定一个搁置的阈值。

速度优化:CPU 优化(下)

经过 Times 函数来核算 CPU 速率,以此判别 CPU 是否现已搁置完成起来比较简单,而且功能也高,所以关于想要判别 CPU 是否搁置来进行预加载使命这一状况,我推荐运用 Times 函数。

只需能判别出 CPU 是否处于空闲状况,咱们就能对运用中许多场景的功能带来很大的提高。咱们在搁置时能够预履行的工作许多,比方预创立页面的 View,预拉取数据,预创立次级页面的要害对象等等。

削减 CPU 的等候

讲完了充分利用 CPU 的搁置时刻,咱们再来看看怎样削减 CPU 的等候。那什么是 CPU 等候呢?它和 CPU 的搁置有什么区别呢?为什么削减 CPU 的等候能提高速度,又要怎样削减呢?带着这几个问题,咱们一同往下看。

从底层来看,CPU 实践上没有等候这种状况,CPU 要么是运转的,要么是搁置的。可是从上层来看,假如某个线程或进程具有 CPU 的时刻片,可是 CPU 却在当时指令段停下来,长时刻无法接着履行后边的代码指令的状况,都能够看做是 CPU 的等候。此刻,CPU 之所无法继续履行后边的代码,或许是因为代码堕入了空循环导致 CPU 空转,或许 CPU 被切走去履行其他线程了。 总的来说,有两种状况常常导致 CPU 等候,一是等候锁,二是等候 IO

等候锁

咱们先来看等候锁。在运用 Java 进行运用开发遇到多线程并发使命时,咱们一般都用 synchronize 来对方法或许数据加锁。当这个锁被某一个线程持有时,另一个线程就需求等候锁释放后,才干对方法和数据进行访问。

恳求 synchronize 锁的流程是这样的:首要判别这个锁是否被其他线程持有,假如持有则经过屡次的循环来判别锁是否释放,这个进程就会导致 CPU 的空转,假如屡次空转后仍是无法取得锁,恳求锁的线程便会堕入休眠并参加等候行列,待锁释放后被唤醒。从这个流程能够看到,恳求锁时,不管是空转仍是休眠都会导致当时线程无法取得 CPU 资源。假如这个线程是中心线程,比方主线程和烘托线程,就会导致运用的体会速度变慢。

锁优化

为了削减抢占和等候锁导致的 CPU 等候,咱们需求对锁进行优化。锁优化是一个很巨大的课题,这儿咱们就不打开具体来讲啦,首要介绍一下锁的优化方法论。

  1. 无锁比有锁好:除了不加锁,还有线程本地存储,偏向锁等计划,都归于无锁优化。
  1. 合理细化锁的粒度:削减同步的代码块数量来优化锁的功能,比方将 Synchronize 锁住整个方法细化成只锁住方法内或许会发生线程安全的代码块。
  1. 合理粗化锁的粒度:经过恰当粗化锁也能优化功能,比方当咱们一起屡次调用 Java API 供给的 StringBuffer.append 方法时,虚拟机会将每个 append 方法内部的锁进行粗化,变成在多个连续的 append 方法都只共用一把锁。
  1. 合理增加锁的数量:和细化锁的粒度类似,只不过是经过增加锁的数量来细化粒度。

假如想要对锁的原理或许优化有更加深入的了解的,能够看看我写的这篇文章《把握Android和Java线程原理下》,这篇文章十分具体的介绍了 Android 中各种锁的原理和优化事例

等候 IO

当一个线程履行 IO 使命时,比方往内存读写数据,此刻实践是 DMA(直接存储器访问) 芯片在履行操作,并不关 CPU 什么事。当时就会呈现两种状况:一是有其他线程履行 CPU 使命,使命调度器会将 CPU 切换给其他线程去运用;二是没有其他 CPU 相关使命,CPU 就会一向等候,直到 DMA 芯片完结读写内存数据的操作,再接着履行后边的代码逻辑。

不管是让 CPU 去等候 IO 完结,仍是让 CPU 切换到其他线程去履行使命,关于这个线程来说,履行完一切指令的时刻变长了,也便是指令履行所耗费的平均时钟周期时刻变长了。假如这个线程是主线程或许烘托线程,相同也会导致运用的体会速度变慢。

那么,怎样才干削减等候 IO 导致的 CPU 运用率下降呢?我在这儿介绍 2 种优化计划,分别是 IO 使命别离和运用协程。 下面咱们一同来具体看看它们。

IO 使命别离

IO 使命别离便是将 IO 的使命从主线程或许主流程中别离出来,单独用 IO 线程池去处理,上一章讲线程池时咱们现已具体说了该怎样去创立一个合适的 IO 线程池,这儿就不重复了。当 IO 线程将这个 IO 使命处理完结,再通知主流程拿处理完结的结果进行接下来的逻辑。

但你或许会有疑问,某些状况下,主线程必须要先拿到 IO 使命的结果,才干进行后边逻辑的话,主线程不仍是需求等候 IO 吗? 实践上,IO 使命别离并不能做到不等候 IO,可是能够缩短咱们等候 IO 的时刻,只需你将主流程中的使命拆分得够细,必定有能够先履行的使命,比方我想烘托某个界面,需求 IO 使命读取界面的展示数据。这个时分,假如咱们能够先将界面创立并烘托出来,然后用默认静态数据代替,等 IO 拿到最新数据后再进行界面的更新,那主线程等候 IO 的时刻就缩短了许多。

协程

现在咱们知道,一个 Java 线程在进行 IO 时实践会堕入休眠来等候 IO 完结。那为什么这个线程要堕入休眠呢?这个线程此刻不能去做其他工作吗?事实上,Java 线程确实做不到。首要这个线程的代码逻辑需求按次序先后履行,此刻代码现已在履行 IO 这一步了,所以线程没法去履行后边或许其他的代码。那么咱们有什么方法能够让线程等候 IO 时去做其他工作吗?有方法,想要完成这个场景,就需求运用 Kotlin 协程了。

一个 Java 线程实践上便是一个进程,进程的状况在许多时分是受操作体系管控的,比方调度器调度的时分,会切换进程状况;等候 IO 时,进程会堕入休眠等等。但 Kotlin 的协程不受内存调度器的约束,Kotlin 的协程不能理解为线程。实践上,当你创立协程时,这些协程实践都在同一个进程上运转,Kotlin 内部完成了调度机制,就像内存的进程调度机制相同,去调度履行这个协程使命。

速度优化:CPU 优化(下)
速度优化:CPU 优化(下)

所以经过协程使命,咱们就能在等候 IO 的时分去履行其他使命,而且进程也不会休眠,而是一向运转的状况。实践上,灵敏的运用协程关于 io 密集型运用来说协助是很大的,会让咱们程序功能体现的更好。协程的具体运用归于 kotlin 的基础知识,这儿就不具体介绍了,建议你能在课后了解了解协程,并在运用中运用几回。

小结

在充分发挥 CPU 的利用率提高速度这两章中,咱们首要介绍了三种计划。为了便利大家温习,我梳理了一张脑图,你能够看看。

速度优化:CPU 优化(下)

这三种计划都是根据怎样充分发挥 CPU 利用率这一底层理论衍生出来的,事实上根据这一点咱们还能衍生出许多其他的计划,比方服务端预处理,线程绑核提高多核并行能力等等。只需咱们能依靠底层理论支撑,就能以不变应万变,设计出一系列优化计划。