我们已经知道了
编译为字节码代码
上一篇文章据说这里的字节码的执行过程在工作内存中,但是Getfield和Putfield的两个指令实际上与主内存相互作用。在这里,计数类的增量方法用作示例。
正是由于CPU高速缓存的存在,在多核环境中将存在明显的问题。此外,存在高速缓存的原因是提高操作效率。现代CPU的速度比我们的记忆快得多。如果每次公交的主要记忆都写在公共汽车中,则会导致执行速度降低很多。接受,我们可以理解木桶的理论。我在这里还画了一张图片,以帮助每个人都理解。
有什么方法可以解决可见性问题?当然,对于Java,我们可以使用挥发性的关键字。
挥发性修改变量具有以下特征
其中,可以将监视器理解为锁定,单人释放是释放锁,监视器获取是为了获取锁。从基因上讲,您可以忽略它,只需要知道有这样的事情即可。
将挥发性修饰符添加到计数后,您会发现在字节代码级别上的唯一更改是为标志添加ACC_Volatile徽标标志。运行时,它将根据此标志自动插入内存屏障,以确保可以看到性语义。有四种类型的记忆障碍,即:
这里有一个文档,可以更威权地解释记忆障碍的知识。您可以继续加深此知识。此处的文档中的一个实例说明了如何插入内存屏障。
编辑
添加图片注释,不超过140个字(可选)
回到上面的示例,在我们将挥发性修饰符添加到计数中后,我们可以在多线程中获得正确的累积结果吗?让我们对其进行测试。简而言之,我们只打开两个线程,每个线程分布得分配了一半的计算量。
结果显然是错误的,并且该程序运行了多次。这是因为波动仅保证可见性,但没有原子语义。例如
在T1-T6时间期间,初始计数= 0,在两个++操作之后,最后一个计数的值仍然为1。在上面的示例中,将以5000万次写入大量类似错误。对于上面分析的挥发性语义,在T5处,Thread1对计数的修改对线程2可见。可以在这里看到,如果您目前调用getfield指令,您所获得的值将是通过Thread1修改修改的最新1个,但不幸的是,Thread2对此一无所知,只需在计数中写下错误的1他自己的步骤。
然后,我们也可以想象,如果存储在当前堆栈中的计数是Putfield之前的最新计数,如果不是计数的最新读取,则可以尝试一下。如果是最新的,请直接编写更新值,似乎就是这样,它可以解决我们上面遇到的错误问题。这似乎是一个好主意,但是必须注意,整个检查过程必须确保原子能,否则仍然存在并发问题。实际上,JDK中不安全的包装中的CAS方法是这个想法,并不断尝试尝试。这个过程是旋转。它的底层依赖于cmpxchgl和cmpxchqq.i使用opekjdk8作为CAS源代码的示例。还有更多的源代码。我只会发布关键代码块。
它的相应本机在热点/src/share/vm/prims/unsafe.cpp中实现
长度的长度,这里仅实现比较。您可以看到原子:: cmpxchg方法被调用,并继续跟随它们
我们跟进以调用CMPXCHG方法。该方法未在atomic.cpp中定义。查看atomic.hpp并查看与cmpxchg相对应的内部连接函数的定义
在这里,我们以Solaris_x86平台为例。与CMPXCHG相对应的内含函数在热点/src/os_cpu/solaris_x86/vm/atomic_x86.inline.hpp中定义
这是嵌入式装配代码。老实说,我还将老师送给了老师。根据lock_if_mp的定义是否是多核的定义,如果是多核,则需要锁定,但是此锁定是CPU总线锁,它的成本比我们的应用程序层的锁定成本要低得多。在同一时间,我们看到了CMPXCHGL的关键指令。当您达到此级别时,我想足够用于应用程序开发工程师。了解基础之后实施,我们来到了当前的销售浪潮。
要使用CAS,我们必须使用不安全的类。我们仍然通过反射获得不安全的对象。首先看不安全类的实现
查看计数类的实现
再次打开两个线程并执行我们的累积程序
可以看出,我们得到了正确的积累,但是运行的长度更长,但是幸运的是,时间复杂性仍然按顺序排列。在这里应该指出的是,在上面的示例代码中,我在此处添加了挥发性关键字。计数变量。实际上,即使没有添加挥发性的关键字,CAS也可以在此处正确工作,但是效率将较低。它将低约5%。您可以考虑为什么不增加挥发性效率?
挥发性关键字还具有禁止说明的语义语义,并且经典应用是DCL单身模式。
在这一点上,我们几乎已经讨论了可见性。在下一篇文章中,我们将讨论“原子性”
Java 300,Java Essential高品质视频
[Java Game Project] 1小时教您使用Java语言制作经典矿山扫描游戏
[Java毕业设计]实际战斗OA办公系统Project_OA员工管理系统Project_java开发
原始:https://juejin.cn/post/710644144741587469