JAVA内存模式-3-顺序一致性

数据竞争与顺序一致性保证

数据竞争:在一个线程中写一个变量,在另一个线程读取同一个变量,而且写和读没有通过同步来排序。(说白了就是共享变量没加锁)

顺序一致性:程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。(这是正确同步线程的标准)

顺序一致性内存模型

顺序一致性内存模型是一个被计算机科学家理想化的理论参考模型,为开发人员提供了极强的内存可见性保证,该模型有2个特性:

1.一个线程中所有操作必须按照程序的顺序来执行。

2.不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序。每个操作都必须原子执行且立刻对所有线程可见。

在概念上,顺序一致性模型有一个单一的全局内存(要求立刻可见),这个内存通过一个左右摆动的开关可以连接到任意一个线程,同时每一个线程必须按照程序的顺序来执行内存读/写。

同步程序的顺序一致性效果

假如我对一段代码添加了“锁”,那么JMM会对这块代码的起始位置、终止位置添加特别处理,这样加锁的代码块内部也可以重排序(当然是没有数据依赖性的前提下),同时也能确保加“锁”的代码块始终在两个特定指令之间来确保同步。

未同步程序的执行特性

对于未同步、未正确同步的多线程程序而言,JMM只提供最小安全性:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值。

为了实现最小安全性,JVM在堆上分配对象时,首先会清零内存空间,然后才在上面分配对象,因此在已经清零的内存空间分配对象时,域的默认初始化已经完成。(这就是上文说的默认值)

这里有个奇怪的现象:JMM不保证对64位的long和double型变量的读写操作具有原子性,为什么呢????

这与CPU处理器总线的工作机制密切相关。数据通过数据总线在CPU与内存之间传递。每一次CPU与内存之间的数据传递都是通过许许多多的读/写操作,这些操作叫做“总线事务”。读操作叫做读事务,写操作叫做写事务。

读事务是内存传输数据到CPU,写事务是CPU传输数据到内存。(这里可以参考JDBC的事务,想想也是,一些列操作无法分割,因为分割后就无法达成业务逻辑)

数据总线会把同步总线事务,说白了就是把众多的总线事务,一个事务一个事务串行执行。这样任意时间点,只有一个处理器能访问内存从而保证单个总线事务内部读/写操作的原子性。

真能扯,抓紧进入正题

在32位的CPU处理器上,要对64位的数据操作具有原子性的话,付出的性能代价太高,因此JAVA语言规范鼓励但不强求JVM对64位long和double变量的写操作具有原子性(官方的意思你尽量原子性吧,实在不行就算了)。

在32位的处理器上,JVM会把一个64位的long和double型变量的写操作拆分成2个32位的写操作来执行,既然是拆的话,那么就有可能这2个32位的写操作被分配到不同的总线事务中,因此,对这64位变量的写操作不具有原子性。

问题:N个线程在运行,线程之间共享变量是long类型且未加锁,假如CPU拆分的话,CPU-A需要把2个写事务通过数据总线刷新到内存中去,但只刷新了一个写事务后,CPU-B中的线程B去访问内存,那数据就脏了,这该如何处理????

答:在JSR-133的旧内存模型中,一个64位的long/double型变量的读操作可以被拆成2个32位的读操作;一个64位的long/double型变量的写操作可以被拆成2个32位的写操作。旧内存模型确实会导致上述问题的发生,但是,JSR-133新内存模型(从JDK5开始)仅仅允许把一个64位long/double型变量的写操作拆分成2个32位的写操作来执行,任意的读操作在JSR-133中都必须具有原子性,即任意数据类型的读操作必须要在同一个读事务中执行,但还是没有完整回答我的问题啊。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值