当前位置: 首页 > 网络应用技术

让我们一起学习并发编程:Java内存模型(6)Final的内存语义语义

时间:2023-03-06 13:49:03 网络应用技术

  上一部分引入了锁和挥发性的内存语义。本文介绍了最终的记忆语义。相反,最终领域的阅读和写作更像是对普通变量的采访。

  对于最终域编译器和处理器,遵循两个重型排序规则

  阅读相对较开,使用代码来解释以上两种重型排序规则:

  假设线程A执行Writer()方法,并且线程B通过这两个线程的交互来执行reader()方法。let显示这两个规则。

  写下最终域的重量分类。通过以下方式,它可以实现:

  现在开始分析Writer()方法:

  首先,假设线程B读数对象与读取对象的成员域之间的参考之间没有沉重的排序,那么下图是执行的可能性

  线程执行的序列图

  读取最终域的规则是,在线程中,第一读对象引号以及第一个读取中包含的最终域的第一个读取。编译器在最终域操作的前面插入负载屏障。

  说明:第一读物中包含的第一读对象引用和最终域。这两个操作之间存在间接依赖关系。

  分析读取器()方法:

  假设线程B中的处理器不符合间接依赖性,并且在执行线程中没有重量排序。此时,有以下执行时间:

  线程执行的序列图

  上图B中线程B中读取对象的一般域由处理器分类为处理器读取对象引用。目前,我尚未通过线程A编写的普通域,因此这是错误的阅读操作。但是,在阅读参考文献后,最终域的阅读规则将“有限”读取最终域的操作读取最终域的对象。目前,最终域已正确初始化。

  总结:

  阅读最终域的规则可以确保在阅读对象的最终域之前,它肯定会读取对该最终域的对象的引用。

  基本数据类型如上所述。如果最终域修改的引号类型是怎么办?

  如下所示,int类型数组的引号变量对应于参考类型,最终域的重分类将以下约束添加到编译器和处理器:

  对于上述过程,假设执行writ1()方法,则执行执行后,线程B执行writer2()方法,并且执行执行后读取器()方法将执行。存在线程:

  报价最终执行顺序图

  对于上述代码,您可以确保读取线程C至少可以看到构造函数中的写入线程A编写最终参考对象的成员域,也就是说,至少设置线程C的值可以看到数组中的0值为0是1.1,写线b是在数组元素上写入的,并且读取线程C可能看不到它。读取线程C.由于编写线程B和读取线程C之间存在数据竞争,因此目前的执行结果是不可预测的。

  目前,如果要确保读取线程C看到数组元素的写入,则可以与挥发性或锁一起实现。

  本文一直说,最终域的重订单规则可以确保在参考变量可见到任何线程之前,参考变量的目标的最终域已在构造函数中正确初始化。如何实现?

  实际上,这需要另一个条件:在构造函数中,其他线程无法看到对构造对象的引用。也就是说,对象引用不能在构造函数中“竖立”。

  示例代码:

  假设线程A执行writer()方法,而线程b执行reader()方法。在此处导致螺纹b在对象未完成结构之前。允许对1和2进行排序,则螺纹b可能不会请参阅正确初始化后最终域的值。实际执行顺序映射可以如下显示:

  多线程执行序列图

  总结:

  在构造函数返回之前,对构造函数的引用无法看到其他线程,因为目前最终域可能无法初始化。构建函数返回后,任何线程都将确保正确域的值是正确的域。

  例如,X86处理器中最终语义的具体实现。

  编译器中将有以下处理:

  但是,由于X86处理器没有对写作编写操作进行排序,因此在X86处理器中,将省略最终域所需的Storestore屏障。类似,因为X86处理器在X86处理器中没有分类间接依赖性的操作,最终域所需的负载屏障将被省略。因此,在X86处理器中,最终域的读取/编写不会插入任何内存屏障。

  在旧的Java内存模型中,最严重的缺陷是最终域的值可能会在场景中发生变化。例如,线程读取最终域的值(初始化之前的默认值)。一段时间后,读取一段时间后最初的最终域的值,但发现它已成为1.为了修复此漏洞,JSR-133可以增强最终语义。

  总结:

  通过将写作和阅读权重的规则添加到Final,您可以为Java程序员提供初始化的安全保证:只要对象正确构造了对象(在构造函数中引用了构造函数,而无需“回声”),则不需要同步启动。(同步原语(使用挥发性和锁定)可以确保任何线程在构造函数中初始化后都可以看到该最终域的值。

  文章总结了“ Java并行编程艺术”,下一篇文章总结了“ Hapens-Be-Before”,因此请继续关注。