volatile关键字

194 阅读3分钟

Java内存模型

        由于程序运行中的变量存放在物理内存中,这就引发一个问题,因为cpu在执行程序指令时,速度非常快(远快于内存),如果一直在物理内存中读取或者写入数据,程序的运行效率大大降低,因此诞生了Java内存模型。

        Java内存模型表示程序运行的变量保存在物理内存中,每一个线程在执行过程中都拥有一个独立的内存(高效缓存)。运行过程中,先从物理内存中拷贝一份变量到本地内存,进行操作后将变量重新写入到物理内存中。

        在多线程环境下,引发另一个问题,缓存一致性问题。比如当有一个共享变量,两个线程分别对其进行自增时,理论值为增加2,但是由于第一个线程在读取变量后并进行自增后,还未写入主存中,第二个线程读取变量也进行自增,之后两个线程都将数据写回主存,这就导致程序执行结束后值只增加1。

为了解决缓存一致性问题,一般有两种方法,基于硬件层面提供的方式:

  1. 给总线lock上锁
  2. 实现缓存一致性协议(当变量在某个线程中执行写操作后,通知其他线程的变量为无效,需要重新读取主存中的值)

并发概念

在并发编程中,一般存在三种问题原子性、可见性、有序性

原子性

表示一系列操作要么全部执行,要么全部不执行。

在Java中,对基本数据类型变量的读取写入均为原子操作,比如i=10为原子操作;i++不是原子操作,包括三步,先从主存获取i,然后+1,最后写回主存。

需要注意的是,在32位系统中,对64位数据进行读取写入不是原子操作。为了保证一个大操作的原子性,一般使用synchronized和lock实现。

可见性

表示一个线程对一个共享变量的值做了修改,其他线程立马可以读取到最新的值,volatile则保证了可见性。

有序性

有序性即程序指令执行的顺序与代码的顺序一致,Java程序在运行过程中,有时为了提高效率,允许编译器和处理器对指令进行重排序,但是最终的结果与代码顺序执行的结果一致。

重排序不影响单线程的执行,但是影响多线程执行的正确性,volatile可以保证程序的有序性。

volatile原理

volatile关键字的作用?

  1. 保证可见性
  2. 保证有序性,禁止代码的重排序

如何保证可见性和有序性?

volatile编译时加入了lock前缀(内存屏障),当一个共享变量被关键字修饰一个线程对其进行修改时

  1. 强制将修改的后值立马刷新到主存中
  2. 通知其他线程变量无效,需要重新读取主存的值
  3. 内存屏障保证程序的有序性,屏障前后的代码不能调换顺序执行

volatile的使用场景

使用volatile具备两个条件(volatile不能保证原子性):

  1. 对变量的写不依赖与当前值
  2. 变量不能存在于其他不变式中

使用场景:

  1. 标识符
  2. double check(单例模式)