在前几篇将Java内存模型的那些事基本上把这个域底层的概念都解释清楚了,聊聊高并发(三十五)Java内存模型那些事(三)理解内存屏障 这篇分析了在X86平台下,volatile,synchronized, CAS操作都是基于Lock前缀的汇编指令来实现的,关于Lock指令有两个要点:

1. lock会锁总线,总线是互斥的,所以lock后面的写操作会写入缓存和内存,可以理解为在lock后面的写缓存和写内存这两个动作称为了一个原子操作。当总线被锁时,其他的CPU是无法使用总线的,也就让其他的读写都等待lock的释放

2. Lock写完后,发起它的CPU的缓存和内存都是最新值,其他CPU相关的缓存行都会invalidate,后续的读/写就会发生缓存不命中,从内存重新加载最新值。

这里有个隐含的点,我没找到具体的资料,但是按照很多资料的说法: volatile的写操作相当于释放锁,volatile的读操作相当于进入锁可以做下面的推断:

volatile操作的是一个变量,而锁保护的程序段中涉及到的变量可以是多个,既然两者的效果是一样的,那么很可能lock后面的写会让高速缓存/写缓存区的所有脏数据都刷新回主存。只有这样volatile在可见性方面和锁保护的程序段的可见性才是行为一致的。

理解这个很重要,因为和这篇讲的Happens-before传递性有关系。Happens-before刚看到的时候从语言上看很难理解,觉得是废话,但是它实际描述的问题其实是可见性的问题,顺带着有一些由于防止重排序而带来的有序性的问题。聊聊高并发(三十三)Java内存模型那些事(一)从一致性(Consistency)的角度理解Java内存模型 这篇说了,内存模型是一致性这个问题域里面的,一致性问题只涉及到了可见性和有序性这两种特性,不包含原子性,所以Happens-before实际上是一系列的一致性的约束,所以它涉及到了可见性和有序性的意思,但没有原子性的含义。

happens-before俗解 这篇文章已经写的很清楚了,我这边再结合上一篇内存屏障的一些概念锦上添花一下,进一步说明这个问题

下面这些Happens-before的规则是从JSR 133 (Java Memory Model) FAQ 摘出来的,一条条看

  • Each action in a thread happens before every action in that thread that comes later in the program's order.
  • 可以理解为对于单个线程来说,前面的写操作对后面都是可见的,这里肯定有人问那指令重排序之后怎么保证这点呢,我也有这个疑问,所以我理解的是如果这个写是同步的,那么对单线程来说,所有同步的写都是按照program order的,这个也是顺序一致性的第一层含义。要理解的是,Java在使用了同步手段之后,被同步保护的点都是保证顺序一致性的。因为同步的底层实现比如内存屏障 / lock都有防止重排序的含义
  • An unlock on a monitor happens before every subsequent lock on that same monitor.
  • 可以理解为一个锁的释放后它前面的写操作对后续进入同一个锁的线程可见,对锁来说这个太肯定了,释放时会lock cmpxchg一次,进入时会lock cmpxchg一次,两次都保证了可见性
  • A write to a volatile field happens before every subsequent read of that same volatile.
  • 可以理解为volatile的写操作对后续的读可见,也是lock addl操作保证了写volatile的可见性
  • A call to start() on a thread happens before any actions in the started thread.
  • 可以理解为线程start()写线程开始状态对后续线程的其他动作可见,JVM内部处理了,实际实现肯定也是用了lock/内存屏障来实现的,其实在聊聊JVM(九)理解进入safepoint时如何让Java线程全部阻塞 中我们提到了线程状态的改变,在JVM里面是对一个线程状态变量进行原子的修改,这个状态的改变是原子的,并且可见的,当然就具备了Happens-before的能力
  • All actions in a thread happen before any other thread successfully returns from a join() on that thread.
  • 可以理解为一个被join的线程中所有的写操作在它join结束后回到原来的线程时,对原来的线程可见。这个和上面的原理差不多,就是JVM在修改线程状态的时候是一次原子操作,并且保证了可见性(估计是一次CAS),所以连带着修改状态前面的修改也都对后续的操作可见了

其他还有一些Happens-before规则,比如CAS操作,原子变量的修改都有Happens-before的含义,另外Happens-before具备传递性,比如 A happens beofre B, B happens before C, 那么A肯定 happens before C。

为什么具备传递性呢,原因还是在开篇的时候说的,lock/内存屏障不仅仅把当前的地址的数据原子的写到缓存和内存,肯定也把这之前CPU缓存/write buffer的脏数据写回到主内存了,这样就实现了Happens before的传递性。

所以所有用到volatile ,synchronized, CAS的地方都具备Happens before的传递性,显式锁和原子变量底层都是基于CAS来实现的,当然用到它们的时候也具备了Happens before的传递性。

所以下面这个例子就很好理解了,比如 y是volatile变量或者是原子变量/同步器类等等用到CAS的

线程A      线程B

x = 1        a = y

y = 2        b = x

如果在时间顺序上y=2这个对被同步的变量的写先发生于 a = y 这个对被同步的变量的读,那么可以肯定的说 b = x = 1。

有人问 x = 1会不会被重排到 y =2 之后,答案是不会,因为y是个被同步的变量,防止重排序, x 不会跨越内存屏障排到y=2之后,所以

b = x同样也不会被重排序到 a = y前面,因为 y是被同步的变量,内存屏障同样不会让屏障后面的操作跨越到前面去

所以只要 y =2 写操作发生在 a = y读操作之前,那么最后 x = 1 肯定先于 b=x,所以 b = 1

参考资料:

happens-before俗解

聊聊高并发(三十六)Java内存模型那些事(四)理解Happens-before规则相关推荐

  1. 聊聊高并发(三十三)Java内存模型那些事(一)从一致性(Consistency)的角度理解Java内存模型

    可以说并发系统要解决的最核心问题之一就是一致性的问题,关于一致性的研究已经有几十年了,有大量的理论,算法支持.这篇说说一致性这个主题一些经常提到的概念,理清Java内存模型在其中的位置. 一致性问题更 ...

  2. 高并发编程-重新认识Java内存模型(JMM)

    文章目录 从CPU到内存模型 内存模型如何确保缓存一致性 并发变成需要解决的问题 (原子性.可见性.有序性) 内存模型需要解决的问题 Java内存模型 JMM的API实现 原子性 synchroniz ...

  3. 聊聊高并发(十六)实现一个简单的可重入锁

    可重入锁指的是假设一个线程已经获得了一个锁,那么它能够多次进入这个锁,当然前提是线程须要先获得这个锁. 可重入锁是最常使用的锁.Java的内置锁就是可重入锁,使用synchronizedkeyword ...

  4. 聊聊高并发(三十五)Java内存模型那些事(三)理解内存屏障

    硬件层提供了一系列的内存屏障 memory barrier / memory fence(Intel的提法)来提供一致性的能力.拿X86平台来说,有几种主要的内存屏障 1. lfence,是一种Loa ...

  5. Java高并发编程(三):Java内存模型

    1 Java内存模型的基础 在并发编程里,需要处理两个问题: 线程之间如何通信 线程之间如何同步. 通信指的是线程之间以何种机制来交换信息.在命令式编程里中,线程之间的通信机制有两种:共享内存和消息传 ...

  6. blp模型 上读下写_Java高并发编程(三):Java内存模型

    1 Java内存模型的基础 在并发编程里,需要处理两个问题: 线程之间如何通信 线程之间如何同步. 通信指的是线程之间以何种机制来交换信息.在命令式编程里中,线程之间的通信机制有两种:共享内存和消息传 ...

  7. Java高并发编程详解系列-内存模型

    volatile关键字介绍,要了解volatile需要了解的还有Java内存模型,以及CPU内存模型等知识.首先从CPU和Java内存模型开始说起. CPU Cache模型   在之前的时候,分享过一 ...

  8. c++ 线程什么时候run_多线程并发支撑基础之JAVA内存模型

    Java内存模型可以说是Java并发的底层支持,了解Java内存模型才能正在了解Java并发. 内存模型 在内存中设置一个变量"value = 1:"那么其他线程能在什么时候读取到 ...

  9. Java并发指南6:Java内存模型JMM总结

    本文转自 https://www.cnblogs.com/kukri/p/9109639.html 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库 ...

最新文章

  1. Http://selboo.com.cn
  2. 在非UI线程中显示Toast
  3. BZOJ 2282 树的直径
  4. 趣学python3(4)-数字,字符串,列表(1)
  5. FZU Problem 2030 括号问题
  6. 谈谈JS里的{ }大括号和[ ]中括号的用法
  7. mysql proxy 多主_mysql多主多从架构与mysql-proxy读写分离
  8. 微课|中学生可以这样学Python(5.8.1节):使用切片访问列表元素
  9. char **p作为参数被修改_opencv第1课-加载、修改、保存图像
  10. vue下载所有格式的文件
  11. 以太网帧各字段的含义_车载以太网(上)
  12. Reflection conclusion
  13. VS2013新建项目出现脚本错误的解决办法
  14. 项目使用微信公众平台图片显示此图片来自微信公众平台 解决方法
  15. 用Python自动生成数据日报!
  16. PAT考试一些注意事项
  17. 在线运行 Linux,真滴牛逼。
  18. MIUI10设置Android通知,Miui10状态栏美化修改工具
  19. 实习篇四-----答辩(月末)
  20. [解锁新姿势] 兄dei,你代码需要优化了

热门文章

  1. android平板2018,荣耀平板5和ipad2018哪个值得买 荣耀平板5和ipad2018哪个好
  2. 成为进阶Linux大佬的第一步
  3. python中lambda函数对时间排序_python – 使用lambda函数排序()
  4. OBD技术速成——J1850协议概述
  5. Java和C/C++程序实时通讯数据移植问题的研究
  6. 世界上有三样东西不能相信(引用)心胸狭窄的男人勿看
  7. 效率 每秒_每秒看懂超过5.3亿张图片!异构计算是如何发挥AI效率的?
  8. 爬虫模拟登陆手机验证码_Python+scrapy爬虫之模拟登陆
  9. java过滤器经典案例_JAVA语言基础的经典案例:猜字母游戏
  10. iview的select联动_iview2 之select二级联动细谈