关于volatile关键字的话,工作中没有遇到使用场景,大部分是面试的时候必问的一个题目;
本文主要内容摘自《JAVA高并发编程详解》

1. CPU Cache模型

CPU Cache主要是为了解决CPU与内存之间访问速度的差异问题,Cache则是在程序运行的过程中会将运算的所需数据从主内存复制一份到CPU Cache中,这样CPU在计算的过程中,可以直接从CPU Cache中读取或写入,当运算结束之后,CPU Cache再将最新数据刷新到主内存当中,CPU直接通过访问Cache的方式替代直接访问主存的方式,极大地提高了CPU的吞吐计算能力;

2.CPU解决缓存一致性问题

CPU Cache虽然解决了CPU与主存访问速度差异,但也引来了另外一个问题,就是缓存的一致性问题;
比如i++的整个计算过程是这样的:

  1. 读取主存i的值到CPU Cache中;
  2. 对i进行加一操作;
  3. 将结果写回到CPU Cache中;
  4. 将数据刷新到主内存中;

在单线程的情况下,这个计算过程没有任何问题,但是在多线程情况下,每个线程都会将i的值加载到自己的本地内存;
比如现在主内存有i=0,线程A与线程B将变量i加载到了自己的工作内存,此时线程A对i加1操作,写回主内存,但是在这时候,线程B感知不到线程A的更改,所以线程B对i加一后,刷新到主内存后,主内存的i还是1;
解决思路:
当CPU在操作Cache中的数据时,如果发现该变量是一个共享变量,也就是说在其他的CPU Cache中也存在一个副本,那么进行如下操作:

  1. 读取操作:不做任何处理,只是将Cache中的数据读取到寄存器;
  2. 写入操作:发出信号通知其他CPU将该变量在Cache中的状态设置为无效,其他CPU在进行该变量读取的时候不得不到主内存中再次获取;

3.java内存模型

Java的内存模型决定了一个线程对共享变量的写入何时对其他线程可见,Java内存模型定义了线程和主内存之间的抽象关系:

  • 共享变量存储于主内存之中,每个线程都可以访问;
  • 每个线程都有私有的工作内存或者称为本地内存;
  • 工作内存只存储该线程对共享变量的副本;
  • 线程不能直接操作主内存,只有先操作了工作内存之后才能写入主内存;
    深入理解volatile关键字_第1张图片

    假设主内存的共享变量x=0,线程A与线程B分别拥有共享变量x的副本,线程A对x做加1操作后,将x的值刷新到主内存中,当线程B想要使用副本x的时候,就会发现变量已经失效了,必须到主内存中再次获取然后存入自己的工作内存中;

4.JMM如何保证并发三大特性

JMM是指 Java Memory Mode指定了Java虚拟机如何与计算机的主内存进行工作;

1) 并发编程的三大特性

(1) 原子性
原子性是指在一次的操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行;

(2) 可见性
可见性是指,当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值;

(3) 有序性
所谓有序性是指程序代码在执行过程中的先后顺序,比如java编译器在优化的过程中会发生指令重排序;

2) JMM保证三大特性

(1) JMM与有序性
对基本数据类型的变量读取赋值操作都是原子性的,对引用类型的变量的读取与赋值的操作也是原子性的;
但是,如果是i++这类的操作,它在底层其它是进行了两步操作的,加一和赋值;
(2) JMM与可见性
java提供了以下三种方式来保证可见性:

  • 使用validate关键字,对于共享变量的写入首先写入到工作内存,但是修改结束后会立刻将其刷新到主内存;
  • 通过加锁的方式实现可见性:无论是synchronized或者Lock,同一时刻只有一个线程获得锁,并且还会确保在锁释放之前,会将对变量的修改刷新到主内存;

(3) JMM与有序性
volatile对一个变量的写操作要早于对这个变量之后的读操作;

5.总结

1.可以保证不同线程之间对共享变量操作时的可见性;
2.可以防止指令重排序;
3.volatile不能保证原子性;