Java并发编程之volatile关键字

时间:2021-05-17 18:02:50

  大概是因为项目、业务的原因,工作上几乎还没有使用过多线程相关的功能,相关知识差不多都忘了,所以最近补一下基础。

  volatile用来修饰共享变量,volatile变量具有 synchronized 的可见性特性,但是不具备原子特性。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。当成员变量发生变化时,强迫线程将变化值回写到共享内存。以保证在同一时刻,不同的线程看到该成员变量的值是一致的。

  因为volatile关键字与Java的内存模型有关,所以先看一下Java内存模型里面的几个概念知识。

  1、可见性

  指多个线程访问同一个变量时,一个线程修改了该变量的值,其他线程立马可以看到该线程修改后的值。

  如下图所示,每个线程都有自己的工作内存,线程访问共享变量时,会先将变量加载到工作内存(如工作内存未缓存该变量),然后对工作内存中的变量副本进行读写操作。假设多个线程同时访问a变量,线程1修改了工作内存中的a变量值,但没有刷新到主内存中,其他线程从主内存缓存的a变量值还是原来的旧值;或者线程1修改a变量的值后刷新到了主内存,但是其他线程工作内存中缓存了线程1修改之前的旧值,其他线程访问该变量时没有从主内存中重新获取,就会出现在多个线程中,同一共享变量的值不一致,即存在可见性问题。

  在 Java 中 volatile、synchronized 和 final 实现可见性。

  Java并发编程之volatile关键字

  2、原子性

  同数据库的原子性,指一个不可拆分的最小单位,原子操作表示一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么全部不执行。

  比如:i = 0;(i非long和double类型)这个操作是不可分割的,那么我们说这个操作是原子操作。

  再比如:i++;实际上等于i = i + 1;先读取i的值,再修改i的值,是可分割的两个操作,所以不是原子操作。

  在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。

  3、有序性

  指程序按照代码的先后顺序执行。

  《深入理解Java虚拟机》中提到:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存中主内存同步延迟”现象。Java 语言提供了 volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile是因为其本身就包含了“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个锁的两个同步块只能串行地进入。

 


一、volatile实现原理

  volatile是轻量级的synchronized,如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。volatile变量修饰的共享变量在进行写操作时,会多一行带有lock前缀的汇编指令,lock前缀的指令在多核处理器下会引发两件事:

  1、  将当前处理器缓存行的数据写回到系统内存。

  Lock前缀指令会引起处理器缓存回写到内存。Lock前缀指令导致在执行指令期间,声言处理器的LOCK#信号。在多处理器环境中,LOCK#信号确保在声言该信号期间,处理器可以独占任何共享内存。

  2、  写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。

  IA-32处理器和Intel 64处 理器使用MESI(修改、独占、共享、无效)控制协议去维护内部缓存和其他处理器缓存的一致性。处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。

二、volatile内存语义

  当声明共享变量为volatile后,就具备了以下两层语义:

  1、保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  2、禁止进行指令重排序。

 

  从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果:volatile写和锁的释放有相同的内存语义;volatile读与锁的获取有相同的内存语义。

  当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。

  当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

 

  为了优化程序性能,编译器重和处理器会对指令序列进行重新排序。但是为了实现volatile内存语义,JMM会分别限制这两种类型的重排序类型。编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

三、volatile使用场景

  虽然说volatile是轻量级的synchronized,但是volatile关键字是无法替代synchronized关键字的。synchronized关键字是防止多个线程同时执行一段代码,会很影响程序执行效率,但是能够保证一组操作的原子性;而volatile对任意单个变量的读/写具有原子性,但对于一组复合操作不具有原子性(例如:i++)。

  要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

  • 对变量的写操作不依赖于当前值。
  • 该变量没有包含在具有其他变量的不变式中。