个人创作约定:本人声明所有文章均为原创。如果有参考任何文章,它将被标记。如有疏漏,欢迎大家批评指正。如果您在网上发现有人抄袭本文,请举报并积极向本github仓库提交issue。感谢大家的支持~本文参考了很多文章、文档和论文,但是这个东西确实比较复杂,本人水平有限,可能理解不到位,如有异议,请留言。本系列会持续更新,在这里结合大家的问题和错误疏漏,欢迎大家留言。喜欢单体版的请访问:全网最硬核的Java新内存模型分析与实验单体版(持续更新在QA)全网hardcoreJava新内存模型——一、什么是Java内存模型全网最hardcoreJava新内存模型分析与实验——二、最硬核Java新内存的Atom访问与分词分析与实验全网内存模型-3.HardCoreUnderstandingMemoryBarrier(CPU+Compiler)全网最难的Java新内存模型分析与实验-4.Java新内存访问方式与实验最难的分析与实验-全网核心Java新内存模型-五、JVM底层内存屏障源码分析JMM相关文档:JavaLanguageSpecificationChapter17TheJSR-133CookbookforCompilerWriters-DougLea'sUsingJDK9MemoryOrderModes-DougLea的内存障碍、CPU和内存模型相关:弱内存模型与强内存模型内存障碍:软件黑客的硬件视图当代ARM和x86架构的详细分析内存模型=指令重新排序+存储原子乱序执行x86CPU相关信息:x86wikiIntel?64andIA-32ArchitecturesSoftwareDeveloperManualsFormalSpecificationofthex86InstructionSetArchitectureARMCPU相关资料:ARMwikiaarch64Cortex-A710Specification各种一致性的理解:CoherenceandConsistencyAleskey大神的JMM解释:AlekseyShipil?v-不要误解Java内存模型(上)AlekseyShipil?v-不要误解Java内存模型(下)相信很多Java开发都会用到Java中的各种并发同步机制,比如volatile、synchronized和Lock等,很多人也看过JSRChapter17Threads和Locks(地址:https://docs.oracle.com/javase/specs/jls/se17/html/jls-17.html),包括同步、Wait/Notify、Sleep&Yield、内存模型等,有做了很多Specification解释。但我也相信大多数人都和我一样。初读时,有种看热闹的感觉。清楚的认识。同时,结合Hotspot的实现和Hotspot源码的解读,我们甚至会发现,由于javac的静态代码编译优化和C1、C2的JIT编译优化,最终的性能代码与我们从规范中理解的不同。代码的行为可能不太一致。而且,这种不一致导致我们去学习Java内存模型(JMM,JavaMemoryModel),了解Java内存模型的设计。如果我们想通过实际代码来尝试,结果是我们可能正确的理解是有偏差的,导致误会。自己也在不断的尝试理解Java内存模型,重读JLS和各位大神的分析。本系列将从阅读这些规格和分析以及jcstress所做的一些实验中整理出一些理解。希望对大家理解Java9之后的Java内存模型和API抽象有所帮助。不过,我还是要强调一下,内存模型的设计是从抽象一些设计开始的,不关心底层。涉及的事情很多。本人水平有限,可能理解不到位。我会尽力解释每一点。所有的论点和参考文献都被提出来了。请不要完全相信这里的所有观点。如有异议,请具体举例反驳并留言。3.原子访问原子访问,对于一个字段的写入和读取,操作本身是原子的,不可分割的。你可能经常没有注意的是,根据JLS第17章的说明,下面两个操作不是原子访问的:因为你现在的系统通常是64位的,得益于此,这两个操作现在大多是原子的.但实际上,按照Java规范,这两个都不是原子的,在32位系统上不能保证原子性。这里我直接引用JLS第17章的一句原话:为了Java编程语言内存模型的目的,对一个non-volatilelongordoublevalue的单次写入被视为两次单独的写入:onetoeach32-bithalf.这可能导致线程从一次写入中看到64位值的前32位,而从另一次写入中看到第二个32位。volatilelong和double值的写入和读取始终是原子的。翻译一下,简单来说,non-volatilelongordouble可能会根据两个独立的32位写入进行更新,所以它是非原子的。volatilelong或double的读写是原子的。这里为了说明我们的原子性,我举了jcstress中的一个例子:我们使用Java832bit的JVM(Java9之后不再支持32位机器)来运行这里的代码,结果是:asyoucan看,结果不仅是我们代码中指定的值-1和0,还有一些中间结果。4.分词撕裂分词撕裂是指如果更新一个字段,数组中的一个元素,会影响另一个字段,数组中的另一个元素的值。例如,处理器不提供写入单个字节的功能。假设最小维度是int,要在这样的处理器上更新字节数组,如果简单的读取字节所在的整个int,更新对应的字节,然后再把整个int写回去,这种做法是有问题的。Java中没有分词。字段和数组元素是独立的。更新一个字段或元素不会影响任何其他字段或元素的读取和更新。为了说明什么是分词,我们举一个不太恰当的例子,即线程不安全的BitSet。BitSet的抽象是一个位的集合(一个0、1,可以理解为布尔集合),底层实现是一个long数组,一个long保存64位,每次update读取这个long然后传位该操作更新相应的位,然后更新回来。界面层次是一层层更新的,但是底层是按照long的维度更新的(因为是最底层的long数组)。显然,如果没有同步锁,并发访问会造成并发安全问题,导致分词:结果是:这里用一个不恰当的例子来说明什么是分词。Java可以保证没有分词。上面对应的BitSet例子是我们尝试更新一个boolean数组,所以resultwillonlybetruetrue:Thisresultwillonlybetruetrue接下来,我们将进入更痛苦的一章,内存障碍,但是你不不得不担心太多。以我个人的经验来看,内存屏障之所以难以理解,是因为基本我不会从底层的细节告诉你,Java已经给你屏蔽了。直接理解会很难说服自己,所以会猜到一些东西造成误会,所以这篇文章就不上来了,扔给你DougLea摘要,继续使用到此为止,有四个记忆障碍在Java中(这四个分别是LoadLoad、StoreStore、LoadStore和StoreLoad。其实从后面的分析可以看出这四个内存屏障的设计对于现在的CPU来说有些落伍了。现在使用更多关于acquire、release和fence)希望能从我读到的底层细节的文章和论文中提炼出一些大家容易理解的东西供大家参考,从而更好更容易地理解内存屏障。微信搜索“我的编程喵”,关注公众号,微信添加作者,天天刷,轻松提升技术,领取各种优惠:我会经常发一些官方社区的各种框架的好消息视频资料和加个人翻译字幕到以下地址(含上公众号),欢迎关注:知乎:https://www.zhihu.com/people/...B站:https://space.bilibili。com/31...
