一、计算机内存模型:

在多CPU的系统中,每个CPU都有多级缓存,一般分为L1、L2、L3缓存,因为这些缓存的存在,提供了数据的访问性能,也减轻了数据总线上数据传输的压力,同时也带来了很多新的挑战,比如两个CPU同时去操作同一个内存地址,会发生什么?在什么条件下,它们可以看到相同的结果?这些都是需要解决的。

所以在CPU的层面,内存模型定义了一个充分必要条件,保证其它CPU的写入动作对该CPU是可见的,而且该CPU的写入动作对其它CPU也是可见的,那这种可见性,应该如何实现呢?

有些处理器提供了强内存模型,所有CPU在任何时候都能看到内存中任意位置相同的值,这种完全是硬件提供的支持。

其它处理器,提供了弱内存模型,需要执行一些特殊指令(就是经常看到或者听到的,memory barriers内存屏障),刷新CPU缓存的数据到内存中,保证这个写操作能够被其它CPU可见,或者将CPU缓存的数据设置为无效状态,保证其它CPU的写操作对本CPU可见。通常这些内存屏障的行为由底层实现,对于上层语言的程序员来说是透明的(不需要太关心具体的内存屏障如何实现)。

前面说到的内存屏障,除了实现CPU之前的数据可见性之外,还有一个重要的职责,可以禁止指令的重排序。

这里说的重排序可以发生在好几个地方:编译器、运行时、JIT等,比如编译器会觉得把一个变量的写操作放在最后会更有效率,编译后,这个指令就在最后了(前提是只要不改变程序的语义,编译器、执行器就可以这样自由的随意优化),一旦编译器对某个变量的写操作进行优化(放到最后),那么在执行之前,另一个线程将不会看到这个执行结果。

当然了,写入动作可能被移到后面,那也有可能被挪到了前面,这样的“优化”有什么影响呢?这种情况下,其它线程可能会在程序实现“发生”之前,看到这个写入动作(这里怎么理解,指令已经执行了,但是在代码层面还没执行到)。通过内存屏障的功能,我们可以禁止一些不必要、或者会带来负面影响的重排序优化,在内存模型的范围内,实现更高的性能,同时保证程序的正确性。

二、缓存一致性:

百度百科:指保留在高速缓存中的共享资源,保持数据一致性的机制。

CPU的高速缓存当中在单线程运行是没有问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的)。这时CPU缓存中的值可能和缓存中的值不一样,这就是著名的缓存一致性问题

缓存一致性可以分为三个层级:

(1)在进行每个写入运算时都立刻采取措施保证数据一致性

(2)每个独立的运算,假如它造成数据值的改变,所有进程都可以看到一致的改变结果

(3)在每次运算之后,不同的进程可能会看到不同的值(这也就是没有一致性的行为)

为了解决缓存不一致性问题,通常来说有以下2种解决方法:

(1)通过在总线加LOCK#锁的方式

(2)通过缓存一致性协议

三、MESI协议

百度百科指MESI协议是基于Invalidate的高速缓存一致性协议,并且是支持回写高速缓存的最常用协议之一。

在该协议的作用下,虽然各cache控制器随时都在监听系统总线,但能监听到的只有读未命中、写未命中以及共享行写命中三种情况。读监听命中的有效行都要进入S态并发出监听命中指示,但M态行要抢先写回主存;写监听命中的有效行都要进入I态,但收到RWITM时的M态行要抢先写回主存。总之监控逻辑并不复杂,增添的系统总线传输开销也不大,但MESI协议却有力地保证了主存块脏拷贝在多cache中的一致性,并能及时写回,保证cache主存存取的正确性。

  • 可见性

Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。

Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

除了volatile,Java中的synchronized和final两个关键字也可以实现可见性。只不过实现方式不同,这里不再展开了。

  • 原子性

在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。在synchronized的实现原理文章中,介绍过,这两个字节码,在Java中对应的关键字就是synchronized。

因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。

  • 顺序性

在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:

volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

  • happens-before

因为jvm会对代码进行编译优化,指令会出现重排序的情况,为了避免编译优化对并发编程安全性的影响,需要happens-before规则定义一些禁止编译优化的场景,保证并发编程的正确性 。

1. 规则一:程序的顺序性规则

一个线程中,按照程序的顺序,前面的操作happens-before后续的任何操作。

对于这一点,可能会有疑问。顺序性是指,我们可以按照顺序推演程序的执行结果,但是编译器未必一定会按照这个顺序编译,但是编译器保证结果一定==顺序推演的结果。

2. 规则二:volatile规则

对一个volatile变量的写操作,happens-before后续对这个变量的读操作。

3. 规则三:传递性规则

如果A happens-before B,B happens-before C,那么A happens-before C。

jdk1.5的增强就体现在这里。回到上面例子中,线程A中,根据规则一,对变量x的写操作是happens-before对变量v的写操作的,根据规则二,对变量v的写操作是happens-before对变量v的读操作的,最后根据规则三,也就是说,线程A对变量x的写操作,一定happens-before线程B对v的读操作,那么线程B在注释处读到的变量x的值,一定是42.

4.规则四:管程中的锁规则

对一个锁的解锁操作,happens-before后续对这个锁的加锁操作。

这一点不难理解。

5.规则五:线程start()规则

主线程A启动线程B,线程B中可以看到主线程启动B之前的操作。也就是start() happens before 线程B中的操作。

6.规则六:线程join()规则

主线程A等待子线程B完成,当子线程B执行完毕后,主线程A可以看到线程B的所有操作。也就是说,子线程B中的任意操作,happens-before join()的返回。

  • 内存屏障

内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。

  • Synchronized

Synchronization有多种语义,其中最容易理解的是互斥,对于一个monitor对象,只能够被一个线程持有,意味着一旦有线程进入了同步代码块,那么其它线程就不能进入直到第一个进入的线程退出代码块(这因为都能理解)。

但是更多的时候,使用synchronization并非单单互斥功能,Synchronization保证了线程在同步块之前或者期间写入动作,对于后续进入该代码块的线程是可见的(又是可见性,不过这里需要注意是对同一个monitor对象而言)。在一个线程退出同步块时,线程释放monitor对象,它的作用是把CPU缓存数据(本地缓存数据)刷新到主内存中,从而实现该线程的行为可以被其它线程看到。在其它线程进入到该代码块时,需要获得monitor对象,它在作用是使CPU缓存失效,从而使变量从主内存中重新加载,然后就可以看到之前线程对该变量的修改。

但从缓存的角度看,似乎这个问题只会影响多处理器的机器,对于单核来说没什么问题,但是别忘了,它还有一个语义是禁止指令的重排序,对于编译器来说,同步块中的代码不会移动到获取和释放monitor外面。

  • Volatile

Volatile字段主要用于线程之间进行通信,volatile字段的每次读行为都能看到其它线程最后一次对该字段的写行为,通过它就可以避免拿到缓存中陈旧数据。它们必须保证在被写入之后,会被刷新到主内存中,这样就可以立即对其它线程可以见。类似的,在读取volatile字段之前,缓存必须是无效的,以保证每次拿到的都是主内存的值,都是最新的值。volatile的内存语义和sychronize获取和释放monitor的实现目的是差不多的。

对于重新排序,volatile也有额外的限制

  • Final

如果一个类包含final字段,且在构造函数中初始化,那么正确的构造一个对象后,final字段被设置后对于其它线程是可见的。

这里所说的正确构造对象,意思是在对象的构造过程中,不允许对该对象进行引用,不然的话,可能存在其它线程在对象还没构造完成时就对该对象进行访问,造成不必要的麻烦。

关于锁,我写过的一篇。

https://blog.csdn.net/qq_41946557/article/details/101098759

无招胜有招之Java进阶JVM(四)内存模型plus相关推荐

  1. java jvm内存模型_Java(JVM)内存模型– Java中的内存管理

    java jvm内存模型 Understanding JVM Memory Model, Java Memory Management are very important if you want t ...

  2. java中的vm自变量_java中JVM虚拟机内存模型详细说明

    java中JVM虚拟机内存模型详细说明 JVM的内部结构如下图: 一个优秀Java程序员,必须了解Java内存模型.GC工作原理,以及如何优化GC的性能.与GC进行有限的交互,有一些应用程序对性能要求 ...

  3. 无招胜有招之Java进阶JVM(三)内存模型

    为什么要有内存模型 在介绍Java内存模型之前,先来看一下到底什么是计算机内存模型,然后再来看Java内存模型在计算机内存模型的基础上做了哪些事情.要说计算机的内存模型,就要说一下一段古老的历史,看一 ...

  4. 【数据湖存储】数据湖的终极奥秘,无招胜有招

    作为海量数据存储与分析的重要承载方式的数据湖,从2011年概念诞生至今,已经发展了9个年头.而数据湖是什么?又能为数字化经济带来什么?<阿里云数据湖存储解决方案蓝皮书>将为您揭开数据湖的终 ...

  5. 无招胜有招——面向对象设计模式随笔,

    最近在学习面向对象设计模式,GOF的23种设计模式大概看了一遍,简单总结一下,设计模式主要分为三种模式,分别是创建模式,结构模式,行为模式. 回过头想一下具体有哪些模式,在什么情况下用什么设计模式,好 ...

  6. "无招胜有招"nbsp;史上最全的互…

    课前秀:三个段子 第一个段子:一个毫无餐饮行业经验的人开了一家餐馆,仅两个月时间,就实现了所在商场餐厅坪效第一名;VC投资6000万,估值4亿元人民币,这家餐厅是雕爷牛腩. 只有12道菜,花了500万 ...

  7. Dirty Approach之无招胜有招

    每个网站,几乎都有"新闻"的模块.在中国独有的ICP--Internet Copy and Past--模式下,多数站点也靠"转贴"手段汇集新闻信息.如果说新浪 ...

  8. Java进阶 JVM 内存与垃圾回收篇(一)

    JVM 1. 引言 1.1 什么是JVM? 定义 Java Vritual Machine - java 程序的运行环境(Java二进制字节码的运行环境) 好处 一次编译 ,到处运行 自动内存管理,垃 ...

  9. Java 并发编程解析 | 如何正确理解Java领域中的内存模型,主要是解决了什么问题?

    写在开头 这些年,随着CPU.内存.I/O 设备都在不断迭代,不断朝着更快的方向努力.在这个快速发展的过程中,有一个核心矛盾一直存在,就是这三者的速度差异.CPU 和内存的速度差异可以形象地描述为:C ...

  10. Java 并发编程解析 , 如何正确理解Java领域中的内存模型

    这些年,随着CPU.内存.I/O 设备都在不断迭代,不断朝着更快的方向努力.在这个快速发展的过程中,有一个核心矛盾一直存在,就是这三者的速度差异.CPU 和内存的速度差异可以形象地描述为:CPU 是天 ...

最新文章

  1. Krona绘制物种或功能组成圈图
  2. Linux基础命令---文本格式转换expand、unexpand
  3. 从近年CVPR看域自适应立体匹配
  4. 7个HTML你可能不知道的使用技巧
  5. 【渝粤教育】电大中专学前儿童健康教育 (4)作业 题库
  6. React Native windows环境安装遇到的坑
  7. Qt图形界面编程入门(7)
  8. java实现中缀表达式转后缀表达式
  9. LayaAir graphics 矢量绘图之直线、折线、曲线
  10. 公钥 私钥_比特币私钥、公钥、钱包地址之间的关系
  11. Elasticsearch和Spring Data Elasticsearch版本对应
  12. html九九乘法口诀表代码,JavaScript九九乘法口诀表的简单实现
  13. 小米运动蓝牙耳机重新配对_小米运动蓝牙耳机怎么连接手机
  14. java 实现超时_Java任务超时处理机制实现
  15. 个人支付接口现状分析——总有一款适合你
  16. css动画走马灯5秒,用animation制作走马灯
  17. c#实现四舍五入(Round)/向上舍入(RoundUp)/向下舍入(RoundDown)
  18. c语言自动安装打印机驱动,安装自定义的即插即用打印机驱动程序
  19. 【C++实验】类和对象(两个分数相加并且化简)
  20. 关于“一个SAPer的网络日志”

热门文章

  1. HDU多校3 - 6975 Forgiving Matching(多项式匹配字符串)
  2. CodeForces - 1217F Forced Online Queries Problem(线段树分治+并查集撤销)
  3. 洛谷 - P4015 运输问题(费用流)
  4. CodeForces - 1090D Similar Arrays(构造+思维)
  5. (转)LCA模板(倍增法)
  6. 中石油训练赛 - 招待(思维)
  7. java 锁的类型_Java锁的种类 - shawnplaying的个人页面 - OSCHINA - 中文开源技术交流社区...
  8. 安卓入门系列-01开发工具Android Studio的安装
  9. 【数据结构】哈夫曼树与哈夫曼编码
  10. 使用pagination分页插件实现Ajax动态分页