JAVA线程安全之volatile

volatile

volatile原理是基于CPU内存屏障(Memory Barrier)指令实现的;

如果一个变量被 volatile 关键字修饰时,那么对这的变量的写是将本地内存中的拷贝刷新到共享内存中;对这个变量的读会有一些不同,读是无视本地内存拷贝,只是从共享变量中去读取数据并拷贝到本地工作内存;

volatile并不能真正保证线程安全,它只能确保一个线程修改了共享数据后,其他线程能看到这个改动,即保证的是可见性,但不能保证原子性

内存可见性

由于Java内存模型(JMM)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存);线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中;

1.这里所提到的主内存可以简单认为是堆内存,而工作内存则可以认为是栈内存;
2.当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据;
3.volatile 修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存中;
JAVA线程安全之volatile_第1张图片
201803201028001.png

volatile关键字的两层语义

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的;
  2. 禁止进行指令重排序;

为什么要使用Volatile

  • Volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里;
  • 线程之间的通信机制有两种:共享内存和消息传递;在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信;

说明:自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存;自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的

下面这段话摘自《深入理解Java虚拟机》:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

使用volatile必须具备以下2个条件:

  1. 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值;
  2. 该变量没有包含在具有其他变量的不变式中

你可能感兴趣的:(JAVA线程安全之volatile)