volatile 的作用和原理

简介: volatile 的作用和原理

volatile 作用


1. 保持内存可见性


内存可见性:所有线程都能看到共享内存的最新状态。每次读取前必须先从主内存刷新最新的值。每次写入后必须立即同步回主内存当中。

volatie  轻量级的 Synchronized , 可以保证共享变量的可见性。也就是说,一个线程能够读取到另外一个线程修改后的值。但是比 synchronized 开销更小。


2.禁止指令重排


volatile关键字提供内存屏障的方式来防止指令被重排,编译器在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

并发编程中, 通常会遇到三个问题:原子性问题,可见性问题,有序性问题。


可见性


可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

线程A 修改一个普通变量的值,然后向主内存进行回写,另外一个线程在线程A回写完之后再对主内存进行读取操作,新变量才会对线程 B 可见。如果读取的不是写完之后的变量,说明新变量对线程B不可见。


原子性


原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

Java 内存模式直接保证的原子性变量操作:read , load, asign , use ,store和 write 这 6个。大致可以认为,基本数据类型的访问,读写都具备原子性。

Java 内存模型可以通过 synchronized 保证原子性。通过monitorenter 和 monitorexit 保证原子性。


有序性


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

Java程序中天然的有序性可以总结为:

  • 如果在本线程内观察,所有的操作都是有序的;
  • 如果在一个线程中观察另一个线程,所有的操作都是无序的。


Java 内存模型


不同计算机操作系统的内存模型是不一样的,这样就需要统一的规范 Java 内存模型(Java Memory Model,JMM).

Java 内存区域:虚拟机栈,本地方法栈,堆,方法区,程序计数器,直接内存。

Java 内存区域和内存模型要有所区别

image.png

Java通过几种原子操作完成工作内存和主内存的交互:


  • lock:作用于主内存,把变量标识为线程独占状态。
  • unlock:作用于主内存,解除独占状态。
  • read:作用主内存,把一个变量的值从主内存传输到线程的工作内存。
  • load:作用于工作内存,把read操作传过来的变量值放入工作内存的变量副本中。
  • use:作用工作内存,把工作内存当中的一个变量值传给执行引擎。
  • assign:作用工作内存,把一个从执行引擎接收到的值赋值给工作内存的变量。
  • store:作用于工作内存的变量,把工作内存的一个变量的值传送到主内存中。
  • write:作用于主内存的变量,把store操作传来的变量的值放入主内存的变量中。


volatile如何保持内存可见性


volatile的特殊规则就是:

  • read、load、use 动作必须连续出现。
  • assign、store、write动作必须连续出现。

所以,使用volatile变量能够保证:

每次读取前必须先从主内存刷新最新的值。每次写入后必须立即同步回主内存当中也就是说,volatile关键字修饰的变量看到的随时是当前变量的最新值。


volatile 如何保证有序性


基于保守策略的JMM内存屏障插入策略:

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

happen-before 原则


  • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  • 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
  • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
  • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始


注意


对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。

volatile不能完全取代Synchronized的位置,只有在一些特殊的场景下,才能适用volatile。总的来说,必须同时满足下面两个条件才能保证在并发环境的线程安全:

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


volatile 的一个作用就是:双检锁实现单例


class Singleton{
    private volatile static Singleton instance = null;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}
相关文章
|
5月前
|
缓存 安全 Java
《volatile使用与学习总结:》多层面分析学习java关键字--volatile
《volatile使用与学习总结:》多层面分析学习java关键字--volatile
31 0
|
2月前
|
存储 缓存 Java
volatile关键字最全原理剖析
【9月更文挑战第27天】`volatile` 是一个用于修饰变量的关键字,告知编译器被修饰的变量可能在程序控制流之外被改变。在多线程环境下,`volatile` 确保变量的值每次从内存中读取,保持最新状态,但不能解决所有同步问题。在硬件交互中,`volatile` 用于内存映射的 I/O 操作,确保读取最新值。不同编程语言如 C/C++ 和 Java 中的 `volatile` 用法略有差异,但都是处理易变变量的重要工具。
|
4月前
|
缓存 安全 Java
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
77 4
|
5月前
|
存储 安全 开发者
JMM的组成和它的作用
JMM的组成和它的作用
47 0
|
6月前
|
缓存 Java 编译器
volatile原理
volatile原理
51 1
|
存储 缓存 安全
深入学习 volatile 的特性
深入学习 volatile 的特性
200 0
深入学习 volatile 的特性
|
安全 Java 编译器
Volatile关键字的作用和实现原理
Volatile关键字的作用和实现原理
184 0
|
缓存 安全 Java
volatile底层的实现原理:volatile关键字的作用、内存模型、JMM规范和CPU指令
volatile底层的实现原理:volatile关键字的作用、内存模型、JMM规范和CPU指令
159 0
|
缓存 Java 调度
volatile 原理
volatile 原理
|
存储 缓存 Java
深入分析Volatile的实现原理
深入分析Volatile的实现原理
75 0