在分享线程的监控以前,咱们要来先讲讲线程的基础知识,通常来讲只要咱们基础牢固,在写代码的时候大部分状况下不容易犯错。但在 Android 团队人数达到几十人甚至上百人的时候,咱们就没法确保全部的同窗都能循序渐进的写好代码了,因此咱们仍是要有监控,但光有监控是不行的还须要有理论基础,这样的话出现了问题才能分析解决。有不少同窗认为线程有什么好了解的,无非就是 synchronized 、volatile、newThread,启动线程。不少同窗可能连线程池和 lock 锁都没接触过,说句实话早先年我也跟你们同样,由于项目中用不上只能本身去看源码学原理。缓存
在过去单 CPU 时代,单任务在一个时间点只能执行单一程序。以后发展到多任务阶段,计算机能在同一时间点并行执行多任务或多进程。虽然并非真正意义上的“同一时间点”,而是多个任务或进程共享一个 CPU,并交由操做系统来完成多任务间对 CPU 的运行切换,以使得每一个任务都有机会得到必定的时间片运行。再后来发展到多线程技术,使得在一个程序内部能拥有多个线程并行执行。一个线程的执行能够被认为是一个 CPU 在执行该程序。当一个程序运行在多线程下,就好像有多个 CPU 在同时执行该程序。多线程比多任务更加有挑战。多线程是在同一个程序内部并行执行,所以会对相同的内存空间进行并发读写操做。这多是在单线程程序中历来不会遇到的问题。其中的一些错误也未必会在单 CPU 机器上出现,由于两个线程历来不会获得真正的并行执行。然而,更现代的计算机伴随着多核 CPU 的出现,也就意味着 不一样的线程能被不一样的 CPU 核获得真正意义的并行执行。因此,在多线程、多任务状况下,线程上下文切换是必须的,然而对于 CPU 架构设计中的概念,应先熟悉了解,这样会有助于理解线程上下文切换原理。安全
多进程多线程在运行的过程当中都离不开一个概念,那就是调度。JVM 虚拟机虽是跨平台可是并未接管线程调度,调度仍是由操做系统自己来决定,咱们在下次看线程建立底层源码便会知道。调度会涉及到一个上下文切换的概念,多任务多线程的本质其实就是 CPU 时间片的轮转,在多任务处理系统中,CPU 须要处理全部程序的操做,当用户来回切换它们时,须要记录这些程序执行到哪里。上下文切换就是这样一个过程,容许 CPU 记录并恢复各类正在运行程序的状态,使它可以完成切换操做。简单一点说就是指 CPU 从一个进程或线程切换到另外一个进程或线程。markdown
在上下文切换过程当中,CPU 会中止处理当前运行的程序,并保存当前程序运行的具体位置以便以后继续运行。从这个角度来看,上下文切换有点像咱们同时阅读几本书,在来回切换书本的同时咱们须要记住每本书当前读到的页码。在程序中,上下文切换过程当中的“页码”信息是保存在进程控制块(PCB, process control block)中的。PCB 还常常被称做“切换桢”(switchframe)。“页码”信息会一直保存到 CPU 的内存中,直到他们被再次使用。PCB 一般是系统内存占用区中的一个连续存区,它存放着操做系统用于描述进程状况及控制进程运行所需的所有信息,它使一个在多道程序环境下不能独立运行的程序成为一个能独立运行的基本单位或一个能与其余进程并发执行的进程。多线程
对于一个正在执行的进程包括 程序计数器、寄存器、变量的当前值等 ,而这些数据都是 保存在 CPU 的寄存器中的,且这些寄存器只能是正在使用 CPU 的进程才能享用,在进程切换时,首先得保存上一个进程的这些数据(便于下次得到 CPU 的使用权时从上次的中断处开始继续顺序执行,而不是返回到进程开始,不然每次进程从新得到 CPU 时所处理的任务都是上一次的重复,可能永远也到不了进程的结束出,由于一个进程几乎不可能执行完全部任务后才释放 CPU ),而后将本次得到 CPU 的进程的这些数据装入 CPU 的寄存器从上次断点处继续执行剩下的任务。架构
上下文切换会带来直接和间接两种因素影响程序性能的消耗。**直接消耗:**指的是 CPU 寄存器须要保存和加载, 系统调度器的代码须要执行, TLB 实例须要从新加载, CPU 的 pipeline 须要刷掉;**间接消耗:**指的是多核的 cache 之间得共享数据, 间接消耗对于程序的影响要看线程工做区操做数据的大小;所以咱们在多线程操做时应该要考虑两个问题:第一个是尽可能减小上下文的切换次数,第二个是尽可能提升 CPU 的使用率。并发
在介绍 Java 内存模型以前,先来看一下到底什么是计算机内存模型,而后再来看 Java 内存模型在计算机内存模型的基础上作了哪些事情。先看一下为何要有内存模型。oop
咱们应该都知道,计算机在执行程序的时候,每条指令都是在 CPU 中执行的,而执行的时候,又免不了要和数据打交道。而计算机上面的数据,是存放在主存当中的,也就是计算机的物理内存啦。刚开始,还相安无事的,可是随着 CPU 技术的发展,CPU 的执行速度愈来愈快。而因为内存的技术并无太大的变化,因此从内存中读取和写入数据的过程和 CPU 的执行速度比起来差距就会愈来愈大,这就致使 CPU 每次操做内存都要耗费不少等待时间。因此,人们想出来了一个好的办法,就是在 CPU 和内存之间增长高速缓存。缓存的概念你们都知道,就是保存一份数据拷贝。它的特色是速度快,内存小,而且昂贵。那么,程序的执行过程就变成了:当程序在运行过程当中,会将运算须要的数据从主存复制一份到 CPU 的高速缓存当中,那么 CPU 进行计算时就能够直接从它的高速缓存读取数据和向其中写入数据,当运算结束以后,再将高速缓存中的数据刷新到主存当中。随着 CPU 能力的不断提高,一层缓存就慢慢的没法知足要求了,就逐渐的衍生出多级缓存。按照数据读取顺序和与CPU结合的紧密程度,CPU 缓存能够分为一级缓存(L1),二级缓存(L3),部分高端 CPU 还具备三级缓存(L3),每一级缓存中所储存的所有数据都是下一级缓存的一部分。这三种缓存的 技术难度和制形成本是相对递减的,因此其容量也是相对递增的。那么,在有了多级缓存以后,程序的执行就变成了:当 CPU 要读取一个数据时,首先从一级缓存中查找,若是没有找到再从二级缓存中查找,若是仍是没有就从三级缓存或内存中查找。单核 CPU 只含有一套L1,L2,L3缓存。若是 CPU 含有多个核心,即多核 CPU,则每一个核心都含有一套L1(甚至和L2)缓存,而共享L3(或者和L2)缓存。性能
从上面的分析来看,这样就会致使一个问题,那就是多线程 CUP 缓存一致性的问题,也就是你们经常说的**原子性问题,可见性问题和有序性问题等等。其本质其实就是 CPU 缓存优化后所带来的后遗症。**出现问题就得解决问题,按照咱们平时普通的思路就是回退版本,废除掉处理器和处理器的优化技术、废除CPU缓存,让CPU直接和主存交互,这确定是不行的。所以内存模型就诞生了,内存模型就是用来解决 CPU 缓存优化后所带来的后遗症。Java 的内存模型以下:优化
Java 后台工程师常常会碰到一些问题像 CPU 飙高、Load 高、响应很慢等等,做为 Android 工程师因为不多会涉及到并发请求的处理,所以咱们不多会刨根问底的去深究线程这一块。虽然遇到的问题可能会千奇百怪可是问题的本质是不会变的,这也是为何我一再强调你们要把基础打牢,要多花些时间在 Linux 内核和系统源码上面。能够这么说,在 Android 场景下咱们遇到的线程问题,只要从 Linux 内核和 JVM 的内存模型这两个方向去分析便可。spa
线程池参数有很是多:核心线程数、最大线程数,队列等等。在实际的过程当中怎么用呢?其实无非就是前面提到的两点:
直接看上去这两点像是冲突了,但在实际的场景中是不冲突的,好比咱们在系统架构时分析过 OkHttp 的源码,咱们不妨来看下它内部使用的线程池:
public synchronized ExecutorService executorService() {
if (executorService == null) {
// 核心线程数是 0 ,最大线程数是 Integer.MAX_VALUE
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
复制代码
synchronized 的底层实现原理之前分析过这里就再也不作过多的介绍,lock 的源码这个须要你们本身去看看,网上有不少的文章你们也能够去辅助了解一下。这里咱们只提一个你们可能没留意的一个区别,synchronized 若是竞争不到锁会致使上下文切换,这也是为何若是没有多线程安全的状况下,就不要随意加锁的缘由。可是 lock 采用的通常是 Unsafe 底层的原理就是等待主线上的数据刷新。看上去好像 Lock 更好些,能够减小上下文的切换次数,其实也不彻底正确,具体场景须要具体对待。
视频连接: pan.baidu.com/s/1pZA2udae… 视频密码: 87uh