Java并发编程(六):从CPU缓存一致性协议到JMM(Java内存模型)

本文介绍了Java并发编程中,从CPU缓存一致性协议到Java内存模型(JMM)的概念。JVM通过JMM确保跨平台的并发正确性,解决多线程环境下缓存一致性问题。文章讲解了CPU缓存如何提升性能,以及多核CPU带来的挑战,引出MESI协议解决一致性问题。此外,JMM作为Java的内存访问规范,规定了工作内存和主内存的交互规则,以保证不同平台的并发行为一致性。
摘要由CSDN通过智能技术生成

注:本系列主要注重并发编程这块儿,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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值