一、现代计算机理论模型与工作原理
冯诺依曼计算机模型
- 控制器(Control)
- 运算器(Datapath)
- 存储器(Memory)
- 输入(Input system)
- 输出(Output system)
现代计算机主要结构基础
CPU数据读取速度: 寄存器 > L1 > L2 > L3 > 内存
CPU缓存是为了弥补内存速度跟不上cpu处理数据速度而诞生的,当今的cpu处理数据速度非常之快,相比之下内存的速度要慢得多,如果cpu直接从内存那里取得数据就会导致cpu大部分时间处于等待状态,浪费资源,于是高速缓存cache就出现了。
-
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果;
-
一级缓存也是内置在CPU内部并且是与CPU同速运行,可以有效的提高CPU的运行效;
-
二级缓存一般都集成在cpu中,但有分为芯片内部和外部两种,集成在芯片内部的二级缓存与CPU同频率二级缓存(即全速二级缓存),而集成在芯片外部的二级缓存的运行频率是CPU的运行频率的一半(即半速二级缓存),因此运行效率较低;
-
三级缓存集成在CPU外部;
-
内存通过总线和CPU交互,所以速度最慢;
在拥有三级缓存的CPU中,其实只有大约5%的数据需要从内存中调用,那么这样也将会是这大大提高了CPU的效率。
读取数据时,CPU依次去寄存器、一级缓存中、二级缓存、三级缓存和内存中去找。从内存依次读取到三级缓存、二级缓存、一级缓存和寄存器中。在数据计算完成后,计算结果被写到内存中的时机是不确定的,如果此时其他的CPU也对数据进行操作,将导致缓存一致性问题。为了让CPU在计算完数据后,立即将计算结果同步到内存中去,出现了Mesi缓存一致性协议。
CPU内部结构
CPU多核(多CPU)缓存架构
为了解决缓存一致性问题,有两种解决办法:
1. 总线加锁
在CPU复制内存中数据到缓存中去使用时,为了避免其他CPU对数据进行操作,会对数据进行加锁,其他CPU对于加锁的数据是不能进行读写操作的。
显而易见,主线加锁导致性能问题,所以目前大部分情况下都不会再进行主线加锁的操作
2. Mesi缓存一致性协议
Mesi代表缓存行1的四种状态,
缓存一致性是通过一个总线来实现的
- 当数据被加载到CPU1缓存中时,如果没有其他CPU复制数据到缓存行中,则CPU1缓存中的缓存行处于独享(E)状态,同时缓存行(总线嗅探机制)监听总线消息;
- CPU2加载数据到缓存中,CPU1和CPU2中的缓存行状态变成共享(S)状态。
- CPU1对缓存行数据进行修改时,缓存行状态变成修改(M)状态;
- CPU1向内存中写数据时,CPU2中的缓存行监听到 bus 的消息时,CPU2中的缓存行变成无效(I)状态;bus 回写数据到主内存中;CPU1中缓存行变成独享(E)状态
- CPU2如果还想要访问数据时,由于缓存行状态为(I),则需要从主内存中再次加载数据,CPU1和CPU2的缓存行状态再次都变成共享(S)状态
当CPU同时修改缓存行状态为独享时,在一个指令周期中,会进行总线裁决,只会有一个指令有效,其余的缓存行置为无效(I)状态。
根据 FIFO、最久未使用法,缓存会被一层一层清理出去。
缓存行失效问题
- 如果数据长度大于一个缓存行,缓存行锁会失效,缓存一致性协议失效,会加总线锁
- CPU本身不支持缓存一致性协议
线程
进程是系统分配资源的基本单位;
线程是调度CPU的基本单元,CPU分配时间片,执行线程中的程序片段。每一个线程都有一个程序计数器、一组寄存器、栈
线程分类:
- 用户级线程:ULT 用户进程自己创建的线程
用户进程中创建多个线程,进程执行时CPU,实际上时进程内部的多个线程在执行,当其中一个线程发生阻塞时,进程内的所有线程阻塞。
好处:避免过度创建线程,避免大量上下文切换;
问题:线程表的管理、调度等需要进程自己实现; - 内核级线程:KLT 只有内核空间才能创建
内核空间中会有一个线程表,所有的线程依附于内核空间,每一个线程可以被视为一个轻量级的进程。
Java1.2之前使用ULT,Java1.2版本之后使用KLT
用户空间划分:
- 内核空间
- 用户空间:用户空间通过内核空间提供的接口,创建内核级线程
CPU特权级别:
- Ring0:只有内核空间才有权限调用
- Ring1
- Ring2
- Ring3:用户空间
Java线程与内核线程的关系
1:1 映射关系
jvm调用操作系统的库调度器,创建对应的内核线程。
Java线程生命状态
为什么用到并发
- 充分利用多核CPU的计算能力
- 方便进行业务拆分。提升应用性能
并发产生的问题
- 高并发场景下,导致频繁的上下文切换;
线程切换时,需要保存线程A的指令、程序指针、中间数据到内核空间中的Tss任务状态段;再去指向性线程B - 临界去线程安全问题,容易出现死锁,产生死锁就会造成应用不可用;
windows使用jstack查看进程状态。 - 其他问题…
JMM模型
JMM围绕原子性、有序性和可见性展开。
JMM是一组规范,屏蔽底层具体实现由不同的JVM去实现。
Java内存模型内存交互操作
- lock(锁定):作用于主内存的变量,把一个变量标记为一条线程独占状态
- unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
- use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
- store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
- write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
Java内存模型只要求上述8大操作(原子操作)必须按顺序执行,而没有保证必须是连续执行
Java内存模型内存同步规则
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或者assign)的变量。即就是对一个变量实施use和store操作之前,必须先自行assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现。
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量之前需要重新执行load或assign操作初始化变量的值。
- 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)
缓存中的最小存储单元,32 | 64 | 128bit ↩︎