仔细看一下Volatile

再说volatile之前,先说一下java内存中是怎么进行处理的?

可以看我以前的文章:java对象创建过程

因为每个线程都有一个本地内存,他们都会缓存一份主内存的共享数据,这样问题就来了,如何保证主内存和线程内存数据一致就成了一个问题?

Volatile关键字可以算是一个轻量级锁,它可以有效的处理上面说的一致性问题,他的实现主要遵循两条原则:

1.Lock前缀指令会引起处理器缓存写回到内存。

2.一个处理器的缓存写到内存会导致其他处理器的缓存无效。

仔细看一下Volatile

通过这个图可以看出,如果线程A和线程B之间要通信的话,必须要经历下面2个步骤:

1.线程A把本地内存A中更新过的共享内存刷新到主内存中去。

2.线程B到主内存中去读取线程A之前已更新过的共享内存。

在第一步的时候,会使用Lock指令,他会锁定这块区域的缓存并写回到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”,缓存一致性会阻止同时修改由两个以上的处理器缓存的内存区域数据。

那么第二步操作时通过使用嗅探技术,发现主内存被修改,然后将自己缓存行设置为无效,强行执行缓存换填充。

Volatile会禁止指令重排序

什么叫做重排序?

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重排序的一种手段。

但是无论怎么重排序,(单线程)程序的执行结果不能改变。

重排序分为三种类型:

1,编译器优化的重排序:在不改变单线程语义的前提下,可以重新安排语句的执行顺序。

2,指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

3,内存系统的重排序,由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作上去可能是乱序执行。

怎么禁止内存重排序的?

编译器在生成字节码的时候,会在指令序列中插入内存屏障来禁止特定的类型处理器重排序。

  • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障。
  • 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。
  • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障。
  • 在每个 volatile 读操作的后面插入一个 LoadStore 屏障。

Volatile有序性实现:

happens-before规则是:对于一个volatile域的写,happens-before于任意后续这个volatile的读。

这种关系具有传递性。