全网最硬核 Java 新内存模型解析与实验 - 4. Java 新内存访问方式与实验(中)

简介: 全网最硬核 Java 新内存模型解析与实验 - 4. Java 新内存访问方式与实验(中)

那么,我们如何保证 Causality 呢?同样的,我们同样不必劳烦 volatile 这么重的操作,采用 release/acquire 模式即可。release/acquire 可以保证 Coherence + Causality。release/acquire 必须成对出现(一个 acquire 对应一个 release),可以将 release 视为前面提到的发射点,acquire 视为前面提到的接收点,那么我们就可以像下图这样实现代码:


image.png


image.png


然后,继续在刚刚的 aarch64 的机器上面执行,结果是:


image.png


可以看出,Causuality 由于使用了 Release/Acquire 保证了 Causality。注意,对于发射点和接收点的选取一定要选好,例如这里我们如果换个位置,那么就不对了:

示例一:发射点只会打包之前的所有更新,对于 x = 1 的更新在发射点之后,相当于没有打包进去,所以还是会出现 1,0 的结果。


image.png


示例二:在接收点会解包,从而让后面的读取看到包里面的结果,对于 x 的读取在接收点之前,相当于没有看到包里面的更新,所以还是会出现 1,0 的结果。


image.png


由此,我们类比下 Doug Lea 的 Java 内存屏障设计,来看看这里究竟用了哪些 Java 中设计的内存屏障。在 Doug Lea 的很早也是很经典的一篇文章中,介绍了 Java 内存模型以及其中的内存屏障设计,提出了四种屏障:


1.LoadLoad

如果有两个完全不相干的互不依赖(即可以乱序执行的)的读取(Load),可以通过 LoadLoad 屏障避免它们的乱序执行(即在 Load(x) 执行之前不会执行 Load(y)):


image.png


2.LoadStore

如果有一个读取(Load)以及一个完全不相干的(即可以乱序执行的)的写入(Store),可以通过 LoadStore 屏障避免它们的乱序执行(即在 Load(x) 执行之前不会执行 Store(y)):


image.png


3.StoreStore

如果有两个完全不相干的互不依赖(即可以乱序执行的)的写入(Store),可以通过 StoreStore 屏障避免它们的乱序执行(即在 Store(x) 执行之前不会执行 Store(y)):


image.png


4.StoreLoad

如果有一个写入(Store)以及一个完全不相干的(即可以乱序执行的)的读取(Load),可以通过 LoadStore 屏障避免它们的乱序执行(即在 Store(x) 执行之前不会执行 Load(y)):


image.png


那么如何通过这些内存屏障实现的 Release/Acquire 呢?我们可以通过前面我们的抽象推出来,首先是发射点。发射点首先是一个 Store,并且保证打包前面的所有,那么不论是 Load 还是 Store 都要打包,都不能跑到后面去,所以需要在 Release 的前面加上 LoadStore,StoreStore 两种内存屏障来实现。同理,接收点是一个 Load,并且保证后面的都能看到包里面的值,那么无论 Load 还是 Store 都不能跑到前面去,所以需要在 Acquire 的后面加上 LoadLoad,LoadStore 两种内存屏障来实现

但是呢我们可以在下一章中看到,其实目前来看这四个内存屏障的设计有些过时了(由于 CPU 的发展以及 C++ 语言的发展) ,JVM 内部用的更多的是 acquire,release,fence 这三个。这里的 acquire 以及 release 其实就是我们这里提到的 Release/Acquire。这三个与传统的四屏障的设计的关系是:


image.png


我们这里知道了 Release/Acquire 的内存屏障,x86 为何没有设置这个内存屏障就没有这种乱序呢?参考前面的 CPU 乱序图:


微信图片_20220625210243.jpg


通过这里我们知道,x86 对于 Store 与 Store,Load 与 Load,Load 与 Store 都不会乱序,所以天然就能保证 Casuality


7.3. Consensus(共识性)与 Volatile


image.png


最后终于来到我们所熟悉的 Volatile 了,Volatile 其实就是在 Release/Acquire 的基础上,进一步保证了 Consensus;Consensus 即所有线程看到的内存更新顺序是一致的,即所有线程看到的内存顺序全局一致,举个例子:假设某个对象字段 int x 初始为 0,int y 也初始为 0,这两个字段不在同一个缓存行中后面的 jcstress 框架会自动帮我们进行缓存行填充),一个线程执行:


image.png


另一个执行:


image.png


在 Java 内存模型下,同样可能有4种结果:

  1. r1 = 1, r2 = 1
  2. r1 = 0, r2 = 1
  3. r1 = 1, r2 = 0
  4. r1 = 0, r2 = 0

第四个结果比较有意思,他是不符合 Consensus 的,因为两个线程看到的更新顺序不一样(第一个线程看到 0 代表他认为 x 的更新是在 y 的更新之前执行的,第二个线程看到 0 代表他认为 y 的更新是在 x 的更新之前执行的)。如果没有乱序,那么肯定不会看到 x, y 都是 0,因为线程 1 和线程 2 都是先更新后读取的。但是也正如前面所有的讲述一样,各种乱序造成了我们可以看大第三个这样的结果。那么 Release/Acquire 能否保证不会出现这样的结果呢?我们来简单分析下,如果对于 x,y 的访问都是 Release/Acquire 模式的,那么线程 1 实际执行的就是:


image.png


这里我们就可以看出来,x = 1 与 int r1 = y 之间没有任何内存屏障,所以实际可能执行的是:


image.png


同理,线程 2 可能执行的是:



image.png


或者:



image.png


这样,就会造成我们可能看到第四种结果。我们通过代码测试下:


image.png


测试结果是:


image.png


如果要保证 Consensus,我们只要保证线程 1 的代码与线程 2 的代码不乱序即可,即在原本的内存屏障的基础上,添加 StoreLoad 内存屏障,即线程 1 执行:


image.png


线程 2 执行:


image.png


这样就能保证不会乱序,这其实就是 volatile 访问了。Volatile 访问即在 Release/Acquire 的基础上增加 StoreLoad 屏障,我们来测试下:


image.png


结果是:


image.png


相关文章
|
13天前
|
Java
Java中ReentrantLock释放锁代码解析
Java中ReentrantLock释放锁代码解析
25 8
|
21天前
|
存储 NoSQL Java
Java数据库编程指南:实现高效数据存储与访问
【4月更文挑战第2天】Java开发者必须掌握数据库编程,尤其是JDBC,它是连接数据库的标准接口。使用Spring JDBC或JPA能简化操作。选择合适的JDBC驱动,如MySQL Connector/J,对性能至关重要。最佳实践包括事务管理、防SQL注入、优化索引和数据库设计。NoSQL数据库如MongoDB也日益重要,Java有对应的驱动支持。理解这些概念和技术是构建高效数据库应用的基础。
Java数据库编程指南:实现高效数据存储与访问
|
5天前
|
安全 Java API
java借助代理ip,解决访问api频繁导致ip被禁的问题
java借助代理ip,解决访问api频繁导致ip被禁的问题
|
6天前
|
Java API 数据库
深入解析:使用JPA进行Java对象关系映射的实践与应用
【4月更文挑战第17天】Java Persistence API (JPA) 是Java EE中的ORM规范,简化数据库操作,让开发者以面向对象方式处理数据,提高效率和代码可读性。它定义了Java对象与数据库表的映射,通过@Entity等注解标记实体类,如User类映射到users表。JPA提供持久化上下文和EntityManager,管理对象生命周期,支持Criteria API和JPQL进行数据库查询。同时,JPA包含事务管理功能,保证数据一致性。使用JPA能降低开发复杂性,但需根据项目需求灵活应用,结合框架如Spring Data JPA,进一步提升开发便捷性。
|
7天前
|
SQL 缓存 Java
Java数据库连接池:优化数据库访问性能
【4月更文挑战第16天】本文探讨了Java数据库连接池的重要性和优势,它能减少延迟、提高效率并增强系统的可伸缩性和稳定性。通过选择如Apache DBCP、C3P0或HikariCP等连接池技术,并进行正确配置和集成,开发者可以优化数据库访问性能。此外,批处理、缓存、索引优化和SQL调整也是提升性能的有效手段。掌握数据库连接池的使用是优化Java企业级应用的关键。
|
11天前
|
Java
Java 15 神秘登场:隐藏类解析未知领域
Java 15 神秘登场:隐藏类解析未知领域
15 0
|
11天前
|
安全 Java 编译器
接口之美,内部之妙:深入解析Java的接口与内部类
接口之美,内部之妙:深入解析Java的接口与内部类
33 0
接口之美,内部之妙:深入解析Java的接口与内部类
|
22天前
|
存储 Java 关系型数据库
实验设备管理系统【GUI/Swing+MySQL】(Java课设)
实验设备管理系统【GUI/Swing+MySQL】(Java课设)
11 0
|
29天前
|
Java 程序员 C#
静态构造方法解析,Java新手必看技能
静态构造方法解析,Java新手必看技能
9 0
|
1月前
|
XML Java 数据格式
使用java解析XML文件的步骤
使用java解析XML文件的步骤
10 0

热门文章

最新文章

推荐镜像

更多