精华内容
下载资源
问答
  • CPU缓存模型 计算机中所有的运算操作都是通过CPU寄存器来完成的,CPU指令的执行涉及到读写内存数据;由于CPU处理速度远大于内存读取数据度可以达到上千倍的差距,传统的FSB直连内存方式来读取内存数据,造成CPU资源...

    CPU缓存模型

    计算机中所有的运算操作都是通过CPU寄存器来完成的,CPU指令的执行涉及到读写内存数据;由于CPU处理速度远大于内存读取数据度可以达到上千倍的差距,传统的FSB直连内存方式来读取内存数据,造成CPU资源浪费,降低了CPU的吐出量;于是就引入了CPU cache模型来解决这个问题;通过在CPU---->主存之间增加缓存的涉及;

    现在的缓存数量到三级了,最靠经CPU的缓存叫L1,依次L2,L3;L1又被划分为Li,Ld两种专门用途的
    缓存

    1. CPU缓存工作方式

    运算时首先,将内存数据复制一份到CPU缓存中,CPU计算时就可以直接从CPU缓存中结束读取数据(读取CPU缓存数据速度远大于内存数据读取速度),当运算后,在将缓存数据重新刷入内存中;

    2. 内存缓存不一致问题

    如果是单线程这种CPU缓存模型工作方式没有任何问题,但是如果是多线程就会出现内存不一致的情况;

    每个线程都有自己即工作内存(本地内存,对应CPU的缓存),即多个线程就存在多个缓存(内存数据副本);比如:i++操作:

    1. 读取主内存的i到缓存
    2. 对i加1操作
    3. 将结果写入缓存中
    4. 将数据重新刷新到内存中

    如果两个线程现在执行这个运算,很有可能i在经过两次自驾操作后仍然是1;

    解决缓存不一致问题:

    1. 总线加锁
    2. 缓存一致协议

    缓存一致协议中最为出名的就是inter的MESI协议:保证每一个缓存中使用的共享变量副本(其他CPU缓存中也存在一个副本)都是一致的:

    1. 读取操作不作任何处理,只是将缓存的数据读取到寄存器;
    2. 写入操作,发信号通知其他CPU将该变量的cache line置为无效状态,其他CPU在该变量的读取时需要在内存中再次获取;

    java内存模型

    Java内存模型指定了JVM如何与内存进行工作;

    Java内存模型决定了一个线程对共享变量的写入何时对对其他线程可见,定义线程和主内存之间的关系:

    • 共享变量存储于主内存中,每个线程都可以访问
    • 每个线程都有自己的工作内存或者叫本地内存
    • 工作内存只存储该线程对共享变量的副本;
    • 线程不能直接操作主内存,只有先操作了工作内存才能写入主内存
    展开全文
  • CPU缓存与Java内存模型

    千次阅读 2018-09-19 20:45:47
    CPU多级缓存 局部性原理: 1)时间局部性:如果某个数据被访问,那么在不久的将来它很可能再次被访问; 2)空间局部性:如果某个数据被访问,那么它相邻的数据很快也可能被访问。 缓存一致性(MESI) 定义了四...

    CPU多级缓存

    局部性原理:

    1. 时间局部性:如果某个数据被访问,那么在不久的将来它很可能再次被访问;
    2. 空间局部性:如果某个数据被访问,那么它相邻的数据很快也可能被访问。

    缓存一致性(MESI)

    定义了四种cache life的四种状态:

    状态描述
    M(Modified)这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。
    E(Exclusive)这行数据有效,数据和内存中的数据一致,数据只存在于本Cache中。
    S(Shared)这行数据有效,数据和内存中的数据一致,数据存在于很多Cache中。
    I(Invalid)这行数据无效。

    CPU多级缓存——乱序执行优化

    硬件内存架构:


    计算机内存模型
    通常情况下,当一个CPU需要读取主存时,它会将主存的部分读到CPU缓存中。它甚至可能将缓存中的部分内容读到它的内部寄存器中,然后在寄存器中执行操作。当CPU需要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。

    一些问题:(多线程环境下尤其)

    缓存一致性问题: 当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致的情况,如果真的发生这种情况,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,这类协议有MSI、MESI等。
    指令重排序问题: 为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。

    Java内存模型(JMM)

    JVM内存模型指的是JVM的内存分区;而Java内存模式是一种虚拟机规范。
    JVM模型
    JVM模型

    JMM规范了Java虚拟机与计算机内存是如何协同工作的: 规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
    JMM模型
    Java内存模型(不仅仅是JVM内存分区): 调用栈和本地变量存放在线程栈上,对象存放在堆上。
    线程模型

    • 某个本地变量可能是原始变量,始终“呆在”线程栈上
    • 某个本地变量可能是一个对象的引用,对象本身依然放在堆(heap)上,但这个引用放在本地变量上。
    • 对象中如果包含方法,这些方法可能包含本地变量,本地变量依然放在线程栈上,即使对象和包含的方法放在堆(heap)上。
    • 对象的成员变量是跟随对象一起放在heap上的,不论这个对象是原始变量或者引用。
    • 存放在heap上的对象可被所持有的这个对象的线程访问,同时该线程可以访问该对象的成员变量;如果两个线程同时调用该类的方法,访问其成员变量,那么这两个线程都拥有了这个成员变量的私有拷贝。

    Java内存模型和硬件内存架构之间的桥接

    JVM与硬件内存架构存在差异;
    硬件内存架构没有区分线程栈和堆;
    对于硬件而言,所有的线程栈和堆都在主内存里,部分线程的栈和堆会存在于CPU寄存器与cache中。

    Java内存模型和硬件内存架构之间的桥接

    抽象角度看,JMM模型中定义了线程与主内存的关系:

    • 线程之间的共享变量存放在主内存中;
    • 每个线程都有一个私有的本地内存(JMM的抽象概念),里面存放了该线程读/写共享变量的拷贝副本;
    • 低层次角度看,主内存就是硬件内存,为了获取更高的运行速度,虚拟机及硬件系统会将工作内存优先存储与寄存器和高速缓存中;
    • JMM中线程的工作内存,是CPU的寄存器和高速缓存的抽象描述;而JVM的静态存储模型(JVM内存模型)只是一种对内存的物理划分而已,它只局限于内存。

    JMM模型下的线程间通信:

    线程间通信必须要经过主内存。
    Java内存模型定义了以下八种操作来完成:

    1. lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
    2. unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
    3. read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
    4. load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
    5. use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
    6. assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
    7. store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
    8. write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
      内存通信操作

    Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

    1. 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
    2. 不允许read和load、store和write操作之一单独出现
    3. 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
    4. 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
    5. 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
    6. 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
    7. 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
    8. 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
    9. 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

    Java内存模型解决的问题

    多线程读同步与可见性
    可见性(共享对象可见性):线程对共享变量修改的可见性。当一个线程修改了共享变量的值,其他线程能够立刻得知这个修改;

    A、线程缓存导致的可见性问题:

    跑在左边CPU的线程拷贝这个共享对象到它的CPU缓存中,然后将count变量的值修改为2。这个修改对跑在右边CPU上的其它线程是不可见的,因为修改后的count的值还没有被刷新回主存中去。

    可见行问题
    解决这种可见性问题:

    1. volatile关键字:volatile关键字可以保证直接从主存中读取一个变量,如果这个变量被修改后,总是会被写回到主存中去。普通变量与volatile变量的区别是:volatile的特殊规则保证了新值能立即同步到主内存,以及每个线程在每次使用volatile变量前都立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。
    2. synchorized关键字:同步块的可见性是由:
      “如果对一个变量执行lock操作,将会清空工作内存中变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值”
      “对一个变量执行unlock操作之前,必须先把此变量的值同步到主内存中(执行store 和write操作)
    3. final关键字:final关键字的可见性是指,被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那么在其他线程就能看见final字段的值(无须同步)
    B、重排序导致的可见性问题:

    Java程序中天然的有序性可以总结为一句话:
    如果在本地线程内观察,所有操作都是有序的(“线程内表现为串行”(Within-Thread As-If-Serial Semantics));
    如果在一个线程中观察另一个线程,所有操作都是无序的(“指令重排序”现象和“线程工作内存与主内存同步延迟”现象)。
    Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性:
    volatile关键字本身就包含了禁止指令重排序的语义
    synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入


    作者:Echo_IX

    展开全文
  • CPU缓存是CPU与内存之间的临时数据交换器,为了解决CPU运行处理速度与内存读写速度不匹配的矛盾——缓存的速度比内存的速度快多了。 上图左侧为简易的高速缓存结构,数据的读取和存储都经过高速缓存Cache,CPU核心与...


    在这里插入图片描述

    CPU多级缓存

    CPU多级缓存概述

    在这里插入图片描述

    为什么CPU缓存会分为一级缓存L1、L2、L3?有什么意义?

    CPU的频率非常快,主存Main Memory跟不上。CPU缓存是CPU与内存之间的临时数据交换器,为了解决CPU运行处理速度与内存读写速度不匹配的矛盾——缓存的速度比内存的速度快多了。

    上图左侧为简易的高速缓存结构,数据的读取和存储都经过高速缓存Cache,CPU核心与高速缓存有一条特殊的快速通道;主存Main Memory与高速缓存都连在系统总线上(BUS)这条总线还用于其它组件的通信。

    在高速缓存出现后不久,系统变得愈加复杂,高速缓存与主存之间的速度差异被拉大,直到加入了L2 Cache ,甚至L3 Cache。它们的作用都是

    • 作为CPU与主内存之间的高速数据缓冲区,L1最靠近CPU核心;L2其次;L3再次。
    • 运行速度方面:L1>L2>L3
    • 容量大小方面:L1<L2<L3

    CPU会先在最快的L1中寻找需要的数据,找不到再去找次快的L2,还找不到再去找L3,L3都没有那就只能去内存找了。如上图右侧。

    在这里插入图片描述


    CPU 多级缓存-缓存一致性协议MESI

    MESI协议的作用:用于保证多个CPU Cache之间缓存共享数据的一致

    MESI 是指4中状态的首字母。每个Cache line有4个状态,可用2个bit表示,它们分别是:

    注: 缓存行(Cache line):缓存存储数据的单元。

    在这里插入图片描述

    状态描述监听任务
    M 修改 (Modified)该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中缓存行必须时刻监听所有试图读该缓存行相对就主存的操作,这种操作必须在缓存将该缓存行写回主存并将状态变成S(共享)状态之前被延迟执行
    E 独享、互斥 (Exclusive)该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中缓存行也必须监听其它缓存读主存中该缓存行的操作,一旦有这种操作,该缓存行需要变成S(共享)状态
    S 共享 (Shared)该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)
    I 无效 (Invalid)该Cache line无效

    触发事件:

    触发事件描述
    本地读取(Local read)本地cache读取本地cache数据
    本地写入(Local write)本地cache写入本地cache数据
    远端读取(Remote read)其它cache读取本地cache数据
    远端写入(Remote write)其它cache写入本地cache数据

    CPU 多级缓存-乱序执行优化-重排序

    处理器为提高运算速度而做出违背代码原有顺序的优化, 线程下的情况下可见性就会出现问题。

    在正常情况下是不对结果造成影响的。在单核时代处理器对结果的优化保证不会远离预期目标,但是在多核环境下却并非如此. 因为在多核条件下会有多个核执行指令,因此每个核的指令都有可能会乱序。另外处理器还引入了L1、L2缓存机制,这就导致了逻辑上后写入的数据不一定最后写入。

    在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分为下面三种:

    1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

    2. 指令级并行的重排序。现代处理器采用了指令集并行技术来将多条指令重叠执行。如果不存在数据依赖,处理器可以改变语句对应机器指令的执行顺序。

    3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能可能是在乱序执行。

    在这里插入图片描述

    编译器重排序属于编译器重排序,指令级并行重排序和内存系统重排序属于处理器重排序,这些重排序可能会导致多线程出现内存可见性问题。


    JAVA内存模型 (JMM)

    上面讲的是硬件CPU的多级缓存,为了屏蔽掉各种系统硬件和操作系统的内存访问差异,以实现Java程序在各大平台都能达到一致的并发效果,Java虚拟机因此定义了Java内存模型,它规范了Java虚拟机与计算机是如何协同工作的。

    接着说上面的重排序,对于JVM来讲是怎样的呢?

    1. 对于编译器,JMM的编译器重排序规则会禁止特点类型的重排序。
    2. 对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特点类型的处理器重排序。
    3. JMM属于语言级的内存屏障,它确保在不同的编译器和不同的处理器平台上,通过禁止特点类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。

    那JMM(Java Memory Model)
    在这里插入图片描述

    关于JVM内存区域中的堆栈请参考 JVM-01Java内存区域与内存溢出异常(上)【运行时区域数据】

    存在堆上的对象,可以被持有这个对象的引用的线程访问。如果两个线程同时访问同一个对象的私有变量,此时这两个线程所拥有的是"这个对象的私有拷贝"。 比如这里的Object3


    计算机硬件架构简易图示

    在这里插入图片描述

    CPU:一个计算机一般有多个CPU,一个CPU还会有多核。因此意味着每个CPU可能都会运行一个线程,所以计算机出现多线程是很有可能的。

    CPU Registers(寄存器):每个CPU都包含一系列的寄存器,它们是CPU内存的基础,CPU在寄存器上执行的 速度远大于在主存上执行的速度,这是因为计算机访问寄存器的速度远大于主存。

    CPU Cache(高速缓存):由于计算机的存储设备与处理器的处理设备有着几个数量级的差距,所以现代计 算机都会加入一层读写速度与处理器处理速度接近相同的高级缓存来作为内存与处理器之间的缓冲,将运算使用到的数据复制到缓存中,让运算能够快速的执行,当运算结束后,再从缓存同步到内存之中,这 样,CPU就不需要等待缓慢的内存读写了主(内)存 。 一个计算机包含一个主存,所有的CPU都可以访问主存,主存比缓存容量大的多(CPU访问缓存层的速度快于访问主存的速度!但通常比访问内存寄存器的速度还是要慢点)。


    通常情况下,当一个CPU要读取主存(RAM - Main Mernory)的时候,它首先会将主存中的数据读 取到CPU缓存中,甚至将缓存内容读到内部寄存器里面,然后再寄存器执行操作,当运行结束后,会将寄存器中的值刷新回缓存中,并在某个时间点将值刷新回主存。


    JAVA内存模型与硬件架构之间的关系

    在这里插入图片描述

    右侧的硬件内存模型是没有区分线程 Stack栈 和 Heap堆,对于硬件而言,所有的栈和堆分布在主存里面,部分栈和堆也可能出现在CPU缓存以及CPU内部的寄存器中。


    Java内存模型的抽象结构

    在这里插入图片描述
    如果线程A和线程B要通信的话,必须要经历下面两个步骤

    1. 线程A把本地内存A中跟新过的共享变量刷新到主内存中去
    2. 线程B到主内存中去读取线程A更新过的共享变量

    使用如下示意图更加清晰
    在这里插入图片描述

    假设这3个内存中x均为0,线程A执行时将更新后的值假设更新为1临时存放到自己的本地内存A中。 当线程A和B需要通信时,线程A首先会把自己本地内存中的修改后的值即1刷新到主内存中,此时主内存中x=1. 随后线程B到主内存中去读取线程A更新后的值,此时线程B的本地内存的x值也变成了1. 【正常情况 A先B后】

    如果线A和线程B同时读取到主内存中的x值,均为0 ,线程A将x值更新为1,放到线程A本地内存,因为线程A和线程B它们之间的数据不可见,线程B并没有等线程A写回主内存之后做更新操作 ,此时线程B也做了同样的更新操作,这个时候线程B的本地内存中x也变成了1 ,因此当线程B操作完成将结果1写回主内存时计数就出现了错误【因为线程B并没有等线程A将更新后数据写会主内存】,正确的情况应该是线程B读取主内存中的1,然后更新为2,再次写会主内存,主内存最后的x=2. 这就引起了并发问题。这就解释了我们案例中的count为啥不总是等于1万的情况 , 案例-> https://blog.csdn.net/yangshangwei/article/details/87400938#_48 【异常情况 AB同时执行】

    所以需要使用同步的手段去确保程序处理的准确性。

    从整体上看,这两个步骤实质上是线程A向线程B发送消息,而且这个通信过程必须要经过主内存。 JMM通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证 。


    Java内存模型的同步八种操作

    在这里插入图片描述

    1. Lock(锁定):作用于主内存的变量,把一个变量标识变为一条线程独占状态

    2. Unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其它线程锁定

    3. Read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用

    4. Load(载入):作用于工作内存的变量,它把Read操作从主内存中得到的变量值放入工作内存的变量副本中

    5. Use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎

    6. Assign(赋值):作用于工作内存的变量,它把一个从执行引擎接受到的值赋值给工作内存的变量

    7. Store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作

    8. Write(写入):作用于主内存的变量,它把Store操作从工作内存中一个变量的值传送到主内存的变量中


    Java内存模型 - 同步规则

    1. 如果要把一个变量从主内存中赋值到工作内存,就需要按顺序得执行read和load操作,如果把变量从工作内 存中同步回主内存中,就要按顺序得执行store和write操作,但java内存模型只要求上述操作必须按顺序执行,没有保证必须是连续执行,也就是说Read和Load、Store和Write之间是可以插入其它指令的
    2. 不允许read和load、store和write操作之一单独出现
    3. 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中
    4. 不允许一个线程无原因地(也就是说必须有assgin操作)把数据从工作内存同步到主内存中
    5. 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了load和assign操作
    6. 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以同时被一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会解锁,lock和unlock必须成对出现
    7. 如果一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎中使用这个变量前需要重新执行 load或assign操作初始化变量的值
    8. 如果一个变量事先没有被lock操作锁定,则不允许它执行unlock操作,也不允许去unlock一个被其它线程锁定的变量
    9. 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(其实就是执行store和write操作之后)

    并发编程优缺点

    在这里插入图片描述


    代码

    https://github.com/yangshangwei/ConcurrencyMaster

    展开全文
  • 注:本系列主要注重并发编程这块儿,JVM内容很多,会另外开专栏总结,此系列可能只是会稍微提及 一、跨平台和JVM 经过前面几篇博文的介绍,...如果想要在其它不同架构的CPU上运行,则需要根据对应的CPU指令集重新...

    注:本系列主要注重并发编程这块儿,JVM内容很多,会另外开专栏总结,此系列可能只是会稍微提及

    一、跨平台和JVM

    经过前面几篇博文的介绍,我们知道,任何编程语言编写的程序要想被计算机执行,都必须被翻译成运行环境的CPU所能识别的一系列指令。这就导致了一个现象:通常情况下,我们的程序被编译后,只能在对应系列的CPU架构上运行。如果想要在其它不同架构的CPU上运行,则需要根据对应的CPU指令集重新对源码进行编译。这个局限性就比较大了,特别是上个世纪末,互联网的迅速发展使这一局限性进一步放大,人们急需一种能够跨平台执行、可移植性强的语言。SUN公司看准时机,拾起了之前被搁置的Oak语言,推出了可以嵌入网页并且可以随同网页在网络上传输的Applet,并将Oak更名为Java,然后在极短的时间内“席卷”IT界。它之所以成功,则主要归功于其跨平台性,而其跨平台性又依赖于JVM(Java虚拟机)。

    Applet“包含”在HTML中,使用<applet>标签标记,当支持它的浏览器遇到该标记时,会下载对应的应用程序代码并在本地计算机上执行。但这并不是没有条件的,如果计算机想执行Applet,那么必须要安装Java运行环境。程序其实是通过JVM以main方法为入口运行的。JVM充当的是程序代码和计算机之间的中间人,我们的JAVA代码被编译成字节码文件(.class)后加载到JVM运行,而JVM会负责将字节码解释成具体平台上的机器指令执行,所以Java也就有了“一次编译,到处运行”的口号。显而易见,它的运行必须依赖运行环境(关于效率等问题,这里不做讨论)。

    JVM是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。其运行时内存划分为如下图中的几个区域(图片来源于百度(1.7)):

    二、CPU和缓存一致性

    由于CPU运算速度要比内存读写速度快的多,所以如果CPU每每执行指令都直接和内存交互的话,则会花费大量的时间等待数据读取和数据写入,在等待的过程中我们的CPU资源则处于“浪费”状态,俗话说浪费可耻,于是乎,CPU高速缓存出现了。高速缓存位于CPU寄存器组和内存之间,它的速度远高于内存但是也比不上CPU内部的寄存器组。如果我们引入了缓存,那么当CPU要使用数据的时候,可以先从缓存中获取,从而加快读取速度。如果缓存没有数据再从内存加载,然后写入缓存,方便后面使用,但是缓存中的一些数据可能过一段时间就不那么常用了,所以需要一定的算法淘汰这些缓存数据,以清理空间。就这样,我们在很大的程度上解决了CPU运算速度与内存读写速度不匹配的矛盾。(这和我们平时项目中在数据库和应用之间加入类似于redis的缓存是一个道理)

    按照数据读取顺序和与CPU结合的紧密程度,CPU缓存还可以进一步分为一级缓存、二级缓存、三级缓存等。当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有找到就从三级缓存或内存中查找。

    有了多级缓存之后,CPU对于数据的读取和存储都会经过高速缓存,CPU与高速缓存有一条特殊的快速通道相连,而主存与高速缓存都连在系统总线上(当然,这条总线还会用于其他组件的通信),如下图所示(网图):

    CPU执行指令的时候如果涉及到数据的读写操作,会将运算需要的数据从主存(内存)复制一份到我们上面提到的高速缓存当中,接下来CPU计算时就可以直接在它的高速缓存上进行数据的读写,当运算结束之后,再将数据从高速缓存刷新到主存当中。比如:i = i + 1;(当然,CPU看见的语句可不是这个样子哈)

    当CPU执行这个语句时,会先从主存(内存)中读取 i 当前的值,然后将其复制一份到高速缓存,这时相当于高速缓存中有了 i 的一个副本,接下来对该副本进行加 1 操作,加法完成后将结果写入高速缓存,最后再将高速缓存中的值刷新到主存。

    在以前的单核单CPU时代,所谓的多线程都不是真正意义上的多线程,因为一个CPU内核在同一个时间点只能执行一个任务,这个时候的多线程是通过分配CPU时间片实现的,执行一个任务的时候,其它任务则等待,只是等待的时间很短,这让我们看起来像是多个线程在同时运行。可是随着制造工艺的发展,现在多核CPU已经成为常态,什么双核、四核等等随处可见。在多核多CPU的场景下,可以实现真正意义的多线程操作,比如两个线程可以运行于一个CPU内的两个不同的内核(这里我们不用在意多CPU或者多核CPU),它们相互独立。

    我们现在要注意前面说到的高速缓存,一级缓存就在CPU内核旁边和其紧密相连,是内核独享的,但是二级缓存和三级缓存等则根据CPU的设计,可能是多核共享的。但这并不是我们现在考虑的重点。以下为网图:

    虽然多线程很爽,但是又不可避免的引出了一个问题,我们以上面 i = i + 1;的例子说明。这条语句乍一看,在单线程下是没有问题的,但是在多线程的情况下则可能变得不一样了。我们设 i 的初始为 1,如果我们有A、B两个线程同时执行这行代码,那么每个线程都会发生上述的计算流程。这个时候可能会出现一个情况(当然还有其它情况会导致类似的结果):

    1> A线程和B线程都从主存中读取了i的值,并且缓存到各自的高速缓存中,此时两个副本都为1;

    2> 然后A线程进行加1操作,副本值变成了2,最后回写到主存中,主存中的值为2;

    3> 此时B线程的副本值还是1,加1操作的结果还是为2,然后将其回写到主存;

    4> 最终主存的值为:2。

    但是我们的预期应该是3才对,因为毕竟执行了两次加1操作嘛。这个就是我们通常说的缓存一致性问题了,在主存中的值可以被所有线程访问,所以我们称之为共享变量,高速缓存中的副本值只能自己访问,所以称之为共享变量的私有拷贝(我们要明确,不论是单核还是多核还是多CPU,涉及到多线程处理,都可能会出问题)。

    缓存一致性的问题归根结底是由多个线程同时操作一个共享变量导致的,所以要解决这个问题其实很“简单”:只需要保证共享变量在被某一个线程操作的时候,其它线程都不能对其操作即可。对应到上面的例子也就是:如果A线程先处理共享变量 i 的值,B线程就只有等待A线程处理,并且A线程将最新的值更新到主存之后,B线程才能获取变量 i,再进行操作,这样就能保证结果不会有问题啦。

    而我们前面讲了,主存和高速缓存连在系统总线(Bus)上,它们通过总线进行通信。所以我们可以通过在总线上加锁(LOCK)的方式达到目的。比如A和B要从主存中获取变量 i 的时候,先在总线上发起LOCK信号,如果LOCK成功才能获取变量 i 的值,否则只能等待LOCK被释放才能从其内存地址获取值。

    这的确从根源上解决了缓存不一致的问题。但继而又引出了另一个问题:在LOCK总线的期间,其它线程都只能等待,不能访问该内存区域,导致了效率低下,又特别是对共享变量根本就只有读操作,不存在写操作的时候,更加让人不能忍受,于是乎,就出现了缓存一致性协议。最出名的也就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其它的CPU中存在该变量的副本,会发出信号通知其它CPU将该变量的缓存行置为无效状态,然后当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,它就会从内存重新读取。下面简单介绍一下MESI。

    >Modified(M):已修改。说明变量已经被持有它的处理器修改,如果一个段处于该状态,那么它在其他处理器缓存中的拷贝马上会变成“失效”(Invalid)状态。另外,已修改缓存段如果被丢弃或标记为失效,那么先要把它的内容回写到内存中。

    >Exclusive(E):独占。只能被一个处理器持有该状态,一旦一个处理器持有了该状态的缓存段,那其它处理器就不能同时持有它。如果其他处理器本就持有了同一缓存段,那么它会马上变成“失效”(Invalid)状态。

    >Shared(S):共享。这种状态下的缓存段只能被读取,不能被写入。

    >Invalid(I):失效。如果缓存段为该状态,就相当于它从来没被加载到过缓存中一样。

    只有当缓存段处于Modified或Exclusive状态时,处理器才算真正“持有”它,才能对其发起写操作,如果它没有持有该缓存段,又想要对其发起写操作,就需要先向总线发出申请:我想要写数据啦,可以不?只有当申请通过后才能执行写操作,同时如果其它处理器存有该变量的私有拷贝, 那么总线会通知它们:这个东西已经被某某持有啦,你们的缓存段即将失效!即处于Invalid状态。这时,该共享变量仅仅在持有它的处理器中有效,所以就不会有问题了。另外如果其它处理器要读取该缓存段,那么Exclusive或Modified状态的缓存段必须变为Shared状态,如果是Modified状态,则还需要将更新后的数据写回主存。

    根据MESI协议,当某一个变量被某一个处理器独占时,其不会出现在其它处理器的有效缓存中,另外,它能保证数据如果被更新,然后被写回主存后,所有处理器的缓存段内容的有效值都和主存中的最新值对应。这就解决了上面的缓存不一致的问题。

    注:其实在内核和缓存之间还存在一个区域叫做存储缓存(Store Buffer),缓存和总线之间还存在一个区域叫做失效队列(Invalid Queue),这两部分区域主要要解决的问题是内核发起通知等待响应导致自身阻塞的问题。它们的存在为指令重排提供了支撑,也正是由于他们的存在,MESI实现的缓存一致性其实是最终一致性,详见Java并发编程(八):volatile使用和原理详解

    三、JMM

    JMM即Java Memory Model(Java内存模型),是一种内存访问规范,定义了程序中共享变量的访问规则(私有变量为线程私有,所以不会有并发问题,比如局部变量等等)。它提出的最初目的是为了支持多线程程序设计,Java也有必要保证Java程序能够屏蔽各种硬件和操作系统的内存访问差异,实现在不同的平台下都能支持正确的并发操作,当然在单核情况下依然有效。

    为什么这么说呢?我们现在单单从多线程程序设计而言,像C语言这种程序语言,它直接依赖计算机操作系统的内存模型来实现并发操作,而对于不同的平台,其内存模型可能会有差异,所以这会导致同样的多线程程序在不同的宿主平台会有不同的表现,要想准确无误就可能需要针对不同的平台来编写程序。而Java号称“一次编译,到处运行”,现在已经有了JVM,所以也需要定义一套自己的内存模型,来保证在多线程程序设计也能在不同的宿主平台达成一致的并发效果。(Java内存模型和JVM运行时内存区域划分是两码事,后面会说明)

    注:无论是JVM规范还是JMM(JMM是JVM规范的一个内容),都只是一些标准,最终会交给实现JVM的厂商自己去具体实现,而不同的厂商实现也可能不一样。所以规范和模型的定制既要严谨,又不能死板,需要给厂商留下足够的发挥空间。

    JMM作为虚拟机规范的一部分内容,规定了:每个线程都有自己的工作内存,工作内存中的“共享变量”只是主存共享变量在本地的私有拷贝,对数据的操作都只能在工作内存中进行,更新之后需要同步回主存,而不同的线程之间无法访问其它线程工作内存里的变量,线程之间值的传递必须通过主存来完成等等。如下图所示(网图):

    上图中的本地内存A、B就是线程各自的工作内存,要注意这个工作内存只是一个抽象的概念,可以把它们理解为CPU中的寄存器组、高速缓存等等的一种抽象描述,目标代码最终还是会被CPU所执行,JVM也是一个应用程序,而程序只是通过指令调用这些硬件完成对应的功能而已,不同的JVM厂商可以有自己的实现;而主存可以和我们的计算机内存类比,但是并不等同,它只是表示JVM使用的内存中的一部分而已。

    按照这个模型,如果线程要操作一个共享变量,它就必须把变量拷贝到自己的工作内存中,更新之后再写回主内存,其它线程也只能从主内存中读取变量到自己的工作内存。我们发现,这个和前面说的CPU内核+高速缓存+内存的操作流程是一样的,所以在多线程的情况下,这里也会出现一样的缓存一致性问题。我们的CPU有MESI协议,同样,JMM也有自己的协议来保证缓存一致性。JMM定义了8种操作和一些规则,下面依次介绍。

    1、lock:锁定

    目标:主内存的变量

    功能:把某一个共享变量标记为某线程独占的状态

    2、unlock:解锁

    目标:主内存的变量

    功能:解锁一个处于lock状态的变量,unlock后的变量才能被其他线程lock

    3、read:读取

    目标:主内存的变量

    功能:把一个变量从主内存传输到线程的工作内存中

    4、load:载入

    目标:工作内存的变量

    功能:它把read操作从主内存中得到的变量值放入工作内存的本地变量副本中

    5、use:使用

    目标:工作内存的变量

    功能:把工作内存中的一个变量值传递给执行引擎,执行引擎遇到需要使用变量的值的指令的时候会触发此操作,比如i=i+1,需要先将i的值传递给执行引擎

    6、assign:赋值

    目标:工作内存的变量

    功能:它把一个从执行引擎接收到的值赋给工作内存的变量,当执行引擎对变量做出更新后,执行赋值操作指令的时候会触发操作,比如i=i+1,需要把i+1的结果赋值给i

    7、store:存储

    目标:工作内存的变量

    功能:把工作内存中的一个变量的值传送到主内存中,以便接下来的write的操作

    8、write:写入

    目标:工作内存的变量

    功能:它把store操作从工作内存中获取的一个变量的值覆盖到主内存的对应变量中

    在说晦涩的规则之前,我们先举个例子来理解一下上面的八个操作,当然这例子并不是特别贴切:

    还记得<<武林外传>>莫小贝捏泥人儿那集吗?有张飞、有岳飞、有王菲...  小贝捏的泥人很形象,但是我们总感觉一些泥人儿差那么一点儿感觉。比如,如果张飞的耳朵的轮廓在多一点儿的话应该会更逼真一点儿。所以我们邀请三个小伙伴(当然我是其中之一)组成了一个团队来检查这些泥人儿,负责给它们补眼、补嘴、补耳等等,好在小贝也乐于配合我们,把她的所有作品都拿了出来放在了桌子上。为了检查补偿工作能够有条不紊的进行,我们让小贝将她的泥人儿们编上号,比如A、B、C等等,然后放在一个大圆桌上,我们三个小伙伴每个人都搬一个小桌子围在大圆桌的周围,同时为了防止发生争抢(如果大家都想给王菲补补眼影的话~~),我们商量,泥人只能由小贝送到我们自己的小桌子上,我们只需要告诉小贝想要哪个泥人即可,而小贝就凭她先听到谁喊的为准咯。同时,补偿工作完成后,新泥人也只能通过小贝重新放回桌子上。另外,小贝为了防止我们乱涂乱画,弄废了她的作品,要求我们如果要改泥人儿,必须先照着泥人捏一个一模一样的,然后在新涅的泥人上涂画,最后她检查没问题才能替换掉之前的泥人儿。规定好之后,我们就开干啦!(假设组员包括我、小明、小华,而我们每个人都有一个御用“整形师”,来负责给泥人修整)

    工作刚开始,我大吼一声,小贝我要A号(因为我看到A号是王菲^_^),小贝看了我一眼,点了点头,此时A就处于被我独占(lock)的状态了,突然,小明却比我吼的更大声:小贝,我也要A号!小贝看了他一样,表示无可奈何:现在A已经被小黄预定(独占)了,除非人家不再用(unlock)A了,我才能给你哦,你声音再大也没用。但是我可没那么傻,好不容易动作快抢到了,咋会放弃呢?所以很顺利的,小贝将A号王菲从大圆桌(主内存)上送(read)到了我的小桌子(工作内存)上,因为我早就想好了要给王菲画画眼影,所以到我手里之后我马上就要开干,碍于小贝的要求,我无奈的拿出一大块儿泥土,在我的桌子(工作内存)上照着王菲重新捏(load)了一个王菲二号(副本)。捏好王菲二号之后,就要准备开始画眼影了,我需要把王菲二号交给(use)我的御用“整形师”,整形师听了我的要求,兢兢业业的给王菲二号画上了眼影,然后交给(assign)了我。我瞅了瞅还是比较满意,然后就叫来了小贝:小贝,我这里修整完毕了!然后我将画完眼影的王菲二号交给小贝,小贝瞅了瞅,也还比较满意,就收下(store)我的王菲二号啦,然后就将我提供的王菲二号打上了(write)A号的标签,之前的王菲只有可怜的被丢弃(覆盖)咯~ 当然,我不会在修整王菲了,所以告诉小贝:贝啊,我不用(unlock)A号了哈!小贝点了点头,表示明白。这时候如果小明还想给王菲再涂涂口红的话,他可以去问小贝要(lock)A号了,而这个A号已经被我画上了眼影。当然如果他动作太慢的话还是“危险”哦。像这样,小组内的每个人都按照我给王菲画眼影的这个流程在有条不紊的工作着,直到所有泥人都修整完毕。

    讲了上面的例子,我们再来从规范的角度来说说这些基本操作。

    > read和load操作,store和write操作,必须按顺序执行。也就是只有先read了才能load,只有先store了才能write。

    类比上面的例子:只有小贝把泥人儿给我们了,我们才能照着泥人儿捏一个新的泥人儿,同时也只有我们把最新的泥人儿给小贝了,小贝才能将其和旧泥人做替换。

    > read和load操作,store和write操作,虽然有顺序要求,但是并不要求必须连续执行,它们之间可以插入其它操作。比如访问两个共享变量:read a,read b,load a, load b,回写同理。

    类比上面的例子:我既想给王菲画眼影,又想给张飞加胡子,可以先让小贝把王菲和张飞的泥人儿都给我,我再一个一个的照着捏新泥人儿,并不要求我必须先把王菲二号弄好了,才去让小贝给我拿张飞。

    > read和load操作,store和write操作,不允许单一出现。也就是不允许一个变量从主内存读取了,但是工作内存不接受,或者从工作内存发起了向主内存的回写操作,但是主内存不接受的情况。

    类比上面的例子:

    我:小贝,我要改王菲! (read)

    小贝:好的!

    我:小贝,我又不想要了...(不执行load

    小贝:我刀呢......

    ----------

    我:小贝,我改好了!

    小贝:嗯,我看看...嗯 可以!(store

    我:好的!

    小贝:等等,不行!!太丑了!(不执行write

    我:我刀呢......

    > 不允许一个线程丢弃它的最近的assign操作,也就是如果变量在工作内存发生了变更,则必须更新回主内存。

    类比上面的例子:

    我:师傅,给她加个眼影!

    师傅:好的,没问题!%&*¥#... 好了,加好了!

    我:好的,给我吧(assign)!嗯... 算了,感觉不好看,扔了算了(丢弃assign操作)

    师傅:我刀呢......

    > 不允许一个线程无原因地把数据从工作内存同步回主内存。也就是一个数据如果没有发生assign操作,不允许回写到主存。

    类比上面的例子:

    我:小贝,我要改王菲!

    小贝:好的,给你!

    我:小贝,我又不想弄了,你放回去吧......(不触发assign,直接回写回主存)

    小贝:玩儿我呢?我刀呢......

    但是我们的确不想改怎么办呢?可以这样操作:

    我:小贝,我要改王菲!

    小贝:好的,给你!

    我:哎,不想改了啊... 哎,师傅,泥人给你,你摸摸就可以了,等会儿在给我,不准问为什么!

    师傅:额(⊙o⊙)…  好吧。¥%& 给你...(assign

    我:嗯,可以。小贝,改好了!

    小贝:嗯,好......

    > 一个新的共享变量只能在主存中产生,不允许在工作内存中使用一个未被初始化(load或assign)的变量。也就是一个变量在实施use和store操作之前,必须先经历过load和assign操作。

    类比上面的例子:我们只能先捏一个王菲二号,然后将二号交给整形师处理,同时交给小贝的泥人儿必须是被“处理”过的。

    > 一个变量在同一时刻只允许一个线程对其进行lock操作,但是lock操作可以被同一个线程重复执行多次,多次执行lock后,必须要执行相同次数的unlock操作,变量才会被解除锁定。这个比较直白,就不类比了哈

    > 如果一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。

    类比上面的例子:比如我们仿制泥人儿失败了,我们可以重新告诉小贝,要求再次修整,这时会把我们当前的成果都清空掉,重头来过

    > 如果一个变量没有被lock,则不允许对其执行unlock操作,也不允许去unlock一个被其它线程lock的变量。

    类比上面的例子:你如果没有持有王菲泥人的锁定,却告诉小贝说你要解锁,怕会被以为是傻子哦~

    > 对一个变量执行unlock之前,必须将该变量同步回主存中(执行store和write操作)。

    类比上面的例子:如果我们处理了泥人,不交给小贝不是白忙活了么~~

    注:JMM虽然要求上述的8个操作都保持原子性,但是基于我们上面所说的规范不能太过死板,这里的原子性也有一些例外:对于64位的数据类型(long和double),如果没有被volatile关键字(后面会单独讲volatile)修饰,则可以将对其的读写操作划分为两次32位的操作来进行。也就是不强制要求read、load、store、write对于它们的原子性,这就是long和double的“非原子性协议”

    你可能会想到,难道多线程操作long和double会有并发问题吗?毕竟如果分为两次32位的操作来进行的话,不论几率多小,我们还是有可能会遇到“一半的值”。我们要知道,虽然规范里没有强制要求其为原子操作,但是官方也建议JVM厂商将它们实现为原子操作,而且现在我们所熟知的商用虚拟机基本上还是都选择将其作为原子操作对待。所以对于long和double的非原子性协议,做个了解就可以了。

    四、JVM内存模型和JMM

    JMM和JVM内存模型是位于不同层面的定义,他们描述的并不是同一个目标,并不能混为一谈。JVM内存模型是模拟计算机,在逻辑上将JVM使用的内存区域做了一个划分,像堆、栈、程序计数器等等(JVM篇章会详细总结)。而JMM可以认为是一种内存使用规范,它是为了多线程程序设计而生的,JVM在实现一些Java提供的关键字,比如synchronized、volatile,需要遵循这些规则。按照JMM的定义,每个线程都有自己的工作内存,线程私有,从逻辑上勉强可以对应JVM中的栈、程序计数器等,但是实际上为了速度,实际上使用的可能是寄存器和高速缓存等等,前面提到了,规范在这里定义的比较宽松,JVM厂商可以自己选择如何去利用计算机硬件资源。而JMM定义中的主存从功能上也勉强可以看做是JVM堆中的实例数据(包括方法区等),但要从JMM的角度看的话,其实对应的就是内存。总的来说JVM内存模型是一种内存划分规范,而JMM更像是一种行为规范。站在JVM内存划分的角度,我们的精力可能在于对象如何分配存放;而站在JMM的角度,我们注重的是如何操作这些对象。

    五、总结

    像这种主存和工作内存的模式,在我们的日常学习工作中也经常都能遇到。比如使用像git这样的的版本管理软件时,分支是存储在远端(主存)的,每个人都可以访问这些远端分支(不考虑权限),如果一个人(线程)要更新分支内容,只能将分支克隆到本地(工作内存),生成一个本地分支(私有拷贝),而每个人不能访问非自己工作内存的本地分支。如果要想把修改的内容同步给其它人,只能先将内容从本地分支同步到远端分支(push),然后其他人再从远端获取(pull)。另外,每个人将自己修改的内容同步到远端的时候,需要保证自己本地分支的内容是最新,不然会有冲突产生。如果所有人都可以直接在远端分支操作的话,同时操作的时候是会乱套的。是不是感觉和上面提到的流程差不多?

    参考:<<深入理解Java虚拟机>>

    注:本文是博主的个人理解,如果有错误的地方,希望大家不吝指出,谢谢

    展开全文
  • CPU的处理速度和内存的访问速度差距太大,于是在CPU和主存之间增加了缓存CPU cache模型如图: Cache的出现解决直接访问内存效率低下。程序运行时,Cache会将运算所需要的数据从主存复制一份到CPU cache中,...
  • CPU缓存

    千次阅读 2012-05-21 09:30:02
    在计算机系统中,CPU高速缓存CPU Cache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近...
  • CPU缓存刷新的误解

    千次阅读 2015-03-01 22:07:33
    原文地址 作者:Mechanical Sympathy 译者:潘曦 校对:Simon-SZ ,方腾飞 即使是资深的技术人员,我经常听到他们谈论某些操作是如何导致一个CPU缓存的...看来这是关于CPU缓存如何工作和缓存子系统如何与执行核
  • CPU缓存 相关CPU术语 CPU缓存一致性协议MESI 带有高速缓存的CPU执行计算的流程 CPU 多级的缓存结构 Java 内存模型 (JMM) 线程通信的两种方式 哪些变量可以共享 JMM概述 Java内存模型的抽象结构示意图 volatile 小...
  • 看了很多关于JMM内存模型的博客,都有提到CPU高缓,但是都基本没有解释他们两者之间是属于什么样的关系,求大佬解答
  • CPU缓存一致性协议MESI - 笔记

    万次阅读 2020-04-08 19:49:19
    CPU缓存一致性协议MESI CPU高速缓存(Cache Memory) CPU为何要有高速缓存 CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其...
  • CPU高速缓存(Cache Memory) CPU为何要有高速缓存 CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算...
  • 看了很多网上讲解java伪共享、缓存行填充和CPU缓存的MESI等等,零零碎碎,目前感觉就这篇文章讲的最清楚,忍不住转载下。 原文如下: 认识CPU Cache CPU Cache概述 随着CPU的频率不断提升,而内存...
  • CPU缓存一致性协议MESI

    千次阅读 2018-09-05 21:46:09
    CPU高速缓存(Cache Memory) CPU为何要有高速缓存 CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算...
  • CPU Cache模型

    2020-06-26 14:06:56
    CPU和主存两边的速度严重的不对等,通过传统FSB(Front Side Bus)直接内存的访问方式很明显会导致CPU资源受到大量的限制,降低CPU整体的吞吐量,于是才有了在CPU和主内存之间增加缓存的设计,缓存有一级缓存(一级...
  • 一、什么是CPU缓存 1. CPU缓存的来历 众所周知,CPU是计算机的大脑,它负责执行程序的指令,而内存负责存数据, 包括程序自身的数据。在很多年前,CPU的频率与内存总线的频率在同一层面上。内存的访问速度仅比寄存器...
  • CPU多级缓存

    千次阅读 2018-11-21 14:14:19
    左图为最简单的高速缓存的配置,数据的读取和存储都经过高速缓存CPU核心与高速缓存有一条特殊的快速通道;主存和高速缓存都连在系统总线上,这条总线还用于其他组件的通信。 高速缓存出现不久,系统变得越来越...
  • CPU模型

    千次阅读 2015-05-10 21:56:46
    有的Hypervisor会简单地将宿主机中CPU的类型和特性直接传递给客户机使用,而QEMU/KVM在默认情况下会向客户机提供一个名为qemu64或qemu32的基本CPU模型。QEMU/KVM的这种策略会带来一些好处,如可以对CPU特性提供
  • CPU缓存一致性协议MESI CPU高速缓存(Cache Memory) CPU为何要有高速缓存 CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其...
  • MESI协议是一个被广泛使用的CPU缓存一致性协议。我们都知道在CPU中存在着多级缓存,缓存级别越低,容量就越小,速度也越快。有了缓存,CPU就不需要每次都向主存读写数据,这提高了CPU的运行速度。然而,在多核CP...
  • 在之前的文章 java并发编程之内存模型 中有对volatile的内存语义进行分析,这里简单的复习一下。volatile的内存语义如下: 对volatile变量的写,对其他线程是可见的,其他线程立马就能读到写入的值。 volatile变量...
  • 什么是伪共享 为了理解“伪共享”,在上一文CPU高速缓存那些... 在这里,我们在来回顾一下CPU高速缓存的知识,在现代计算机中,CPU缓存是分层次结构的,例如:L1,L2,L3,当CPU发起一个读取内存指令的时候,首先...
  • 运作原理:通常情况下,当一个CPU需要读取主存时,它会将主存的部分读到CPU缓存中。它甚至可能将缓存中的部分内容读到它的内部寄存器中,然后在寄存器中执行操作。当CPU需要将结果写回到主存中去时,它会将内部...
  • 在上一篇聊聊高并发(三十三)从一致性(Consistency)的角度理解Java内存模型 我们说了Java内存模型是一个语言级别的内存模型抽象,它屏蔽了底层硬件实现内存一致性需求的差异,提供了对上层的统一的接口来提供保证...
  • 前面我们从操作系统底层了解了现代计算机结构模型中的CPU指令结构、CPU缓存结构、CPU运行调度以及操作系统内存管理,并且学习了Java内存模型(JMM)和 volatile 关键字的一些特性。本篇来深入理解CPU缓存一致性协议(..
  • 计算机原理之CPU模型

    2020-11-21 19:03:22
    因此,寄存器文件就是 L1的高速缓存, L1 是 L2 的高速缓存, L2 是 L3 的高速缓存, L3 是主存的高速缓存,而主存又是磁盘的高速缓存。在某些具有分布式文件系统的网络系统中,本地磁盘就是存储在其他系统中磁盘上...
  • 目录 1. 前言 2 2. 结论 2 3. volatile应用场景 3 4. 内存屏障(Memory Barrier) 4 5. setjmp和longjmp 4 ...1) 结果1(非优化编译:g++ -g -o x x.cpp -O0) 5 ...6. 不同CPU架构的一致性模型 6 7. x86-TSO 7 ...
  • 一级数据缓存用于暂时存储并向CPU递送运算所需数据,这就是一级缓存的作用。 二级缓存就是一级缓存的缓冲器:一级缓存制造成本很高因此它的容量有限,二级缓 存的作用就是存储那些CPU处理时需要用到、一级缓存又...
  • 为什么需要CPU多级缓存 为啥要需要缓存Cache cache容量有限,命中率低,为啥还要他。 缓存一致性 MESI 开始详细说一下cpu、内存、缓存之间的关系 如果只是读缓存的情况 如果考虑写缓存的情况 一致性...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 131,539
精华内容 52,615
关键字:

cpu缓存模型