Java关键字volatile,原子性,变量可见性

  1. 内存模型与CPU缓存

本来CPU计算的数字都是从主从main memory中读取的,但是CPU运行的速度比计算机读取内存的速度快,为了补齐这个短板,所以出现了CPU缓从这种东西。

在多CPU系统(或多核处理器——一个芯片上有多个CPU),每个CPU有自己的缓存。两个线程A,B在不同的CPU上同时跑,A对主存的某个共享变量修改后会暂时存在CPU a的缓存中。线程B在CPU b上跑,B仍旧是从主存中读取该共享变量,此时B读到的就是旧值了。就出现了数据的不一致性。

这里出现不一致的条件:必须是多个线程并且访问共享变量,而不是普通变量。

 为了解决这个问题,有两种方式:

在总线上加LOCK#锁;

使用缓从一致性协议,比如MESI协议。


2. 并发环境下的可见性、原子性、有序性

原子性:一个操作,要么执行,要么不执行,在执行的过程中不会被打断。

JAVA原子性适用于除了long和double的原始数据类型的“简单操作”。从内存中读写除了long和double的原始数据类型是原子操作的。

简单操作指的是,简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)

i++            # java中不是原子的  
i = i+1        # java中不是原子的  
i = j          # java中不是原子的  
i = 1          # java中是原子的  
上面四个语句只有最后一个是原子的。这里注意:i=j,虽然不是原子的(有两步:先从内存读取 j 的值,再将 j 的值写入内存),但是这里的每一步是原子的(这两步都是简单的读写操作)。i++在C++里可能就是原子的,这个与C++本身的内存模型有关。

在32位上,对64bit的long和double变量的读写是分成两个32bit读写的,因此上下文切换可能发生在读(或写)进行到一半时,这叫做word tearing。

用violate定义long或者double变量时, 对 “简单的” 赋值和return操作能保证原子性。

不同的JVM提供了对原子性不同程度的保证。。。


可见性:一个线程对共享变量的修改应该被应用内的其他线程立即可见。

有序性:程序执行的顺序按照代码的先后顺序执行。


这里只讨论原子性和有序性。

原子性、可见性

原子性与可见性是两个不同的概念!


Java中原子性保证:Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。


Java中可见性保证:synchronized和Lock、volatile三种。推荐synchronized方式,volatile有局限性,适合某个特定场合。

Java的volatile关键字

可见性和原子性是不同的两个概念。对一个非volatile变量的原子操作的结果不会立即刷新到主从。多线程环境下对某个共享变量访问时,要么把这个变量定义成volatile的,要么使用synchronization,这样就保证了该变量的可见性。但如果该变量的操作是在synchronized方法或代码块中的话,就不用将该变量定义为volatile了,因为synchronization同步机制会强迫刷新到内存,强迫一个线程对共享变量做的改变对整个应用可见。

猜你喜欢

转载自blog.csdn.net/qq_18975791/article/details/80675805