Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

本文需要了解:Java内存模型

缓存一致性问题

为了提高CPU从内存中读写的速度,在CPU和和内存中间添加了缓存,有一级缓存,二级缓存,甚至三级缓存,从一级到三级,容量变大,速度变低。对于多个CPU场景,一般是每个CPU都有自己的一级和二级缓存而公用三级缓存。

在多CPU多线程的读写场景下,多个线程有可能会访问同一块内存区域,以写操作为例,多个线程都会把内存中的值读到自己的CPU中,然后修改,然后暂时放到自己的缓存中,这样就导致每个线程保的缓存中对同一变量可能有不同值的现象,即缓存一致性问题。

缓存一致性协议

为了解决缓存一致性问题,通常有两种方法:

  1. 在总线加LOCK锁
  2. 缓存一致性协议

在总线加锁是一种阻塞的方式,显然是不高效的。

缓存一致性协议(Cache Coherence Protocol),最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。

MESI的核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

volatile关键字

内存语义

  1. 可见性
  2. 有序性

下面这段话摘自《深入理解Java虚拟机》:

  “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

  lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

  2)它会强制将对缓存的修改操作立即写入主存;

  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

volatile是不能保证原子性的,在指令层面,原子性是通过monitorentermonitorexit两个指令实现的,而volatile的指令层面只有lock指令.

在以下两个场景中可以使用volatile来代替synchronized

1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程会修改变量的值。

2、变量不需要与其他状态变量共同参与不变约束。

比如说,作为线程退出的标记量,或者DCL的单例模式中的使用。

既生synchronized,何生volatile

说到底,synchronized是通过加锁来实现原子性、可见性和有序性的。既然是锁,肯定是有开销的,虽然说自1.6开始又给synchronized添加了轻量级锁,偏向锁,但性能损耗还是有的。而且,加锁其实是一个阻塞的过程。

相比之下,关于二者的性能对比,由于虚拟机对锁实行的许多消除和优化,使得我们很难量化这两者之间的性能差距,但是我们可以确定的一个基本原则是:volatile变量的读操作的性能消耗与普通变量几乎无差别,但是写操作由于需要插入内存屏障所以会慢一些,即便如此,volatile在大多数场景下也比锁的开销要低。

评论