乐趣区

Java-基础-关键字volatile

Java 内存模型
Java 内存模型定义:屏蔽掉各种硬件和操作系统的内存访问差异,实现让 Java 程序在各种平台下能达到一致的内存访问效果。避免更换平台后在并发问题上出现访问错误。
在 Java 内存模型中规定所有变量存储在主内存中,每一个线程都有自己工作内存,每个线程都有对变量的读取操作都是在自己的工作内存中操作,线程之间不会有通讯,比如线程 A 对一个变量进行修改,此时的线程 B 并不知道线程 A 在对变量进行修改。线程对变量处理完成后到会写到主内存中,主内存内实现线程之间的变量值传递。
下图为线程、工作内存、主内存之间的关系
volatile
在 Java 内存模型中每个线程都有自己的工作内存,比如在并发情况下的(i++),当线程 A 先进行 i++,此时是在自己的工作内存中进行运算,还没有写到主内存中,线程 B 也在进行 i++,也在对 i 变量做更改,再写入主内存中,就会造成数据不一致。只要让在并发的过程中数据进行同步就可避免问题出现,在 Java 中提供的关键字 volatile 就可以解决此类问题
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:

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

主要用于处理多线程问题,处理指令重排序问题,在 Java 1.5 之后解决指令重排序问题,实现多线程下的单例模式。能保证可见性,有序性,但不保证原子性。
第二条中说禁止进行指令重排序在多线程的单例模式中发挥重要的作用, 指令重排序主要是指在硬件方面,CPU 采用了容许将多条指令不按规定顺序分开发送给各相应电路单元处理,并不是指令任意重排,对于两条指令有依赖,还会按原来顺序处理,只会对没有依赖的指令进行重排,比如在多线程情况下解决以下代码问题
singleton = new Singleton();

在单线程情况下,该段代码会分成三部

分配内存
实例化对象
将实例化对象给 singleton 赋值

这三个过程是没有依赖,在工作内存中无论怎样排序执行都不会影响最后写入到主内存中的数据,但是在多线程情况下,比如多线程单例模式中的,双重检查锁,如果没有之前的 volatile 修饰变量,会出现线程 A 进入实例化代码块内,进行实例化,此时有可能重排序,先执行第三步,此时的 singleton 还是为空,线程 B 进行判断为空,也会进入执行代码块内,就会出现实例化多个对象,违反了单例模式的定义。
解决此类问题可以使用,关键字 volatile,它的工作原理是,当多个线程进行处理同一变量,在执行被 volatile 关键字修饰的代码时,会在编译代码后添加一句 lock add1 $0x0, (%esp),充当一个内存屏障操作,保证线程之间的可见性。
内存屏障(Memory Barrier)是一类同步屏障指令,是 CPU 或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。
加上内存屏障之后,其实实例化代码还是会重排序,但是一旦一个线程进行访问处理,其他线程就不会执行同样的代码块,相当于一但开始对被 volatile 修饰的变量进行处理,改变量的读写操作只容许一个线程访问,其他线程没有权利进行读写操作。
volatile 是通过内存屏障来来禁止指令重排的。
参考《深入理解 Java 虚拟机》

退出移动版