手撕面试题之volatile详解(一)

时间:2024-05-23 15:16:24

有一天,张三去某互联网公司面试:

面试官:你好,张同学,请问你知道java有哪些关键字可以保证线程安全吗?
张三:知道!synchronized关键字可以保证!
面试官:还有呢?
张三:…
面试官:是你自己出门还是我送?

首先,java中除了synchronized关键字可以保证线程安全,还有一个关键字volatile也可以保证。你可以理解它是一个轻量级的synchronized,但是它不能保证线程的原子性。至于为什么我们稍后再说。在了解volatile之前,我们需要知道JMM内存模型与CPU和内存

CPU和内存

在我们现代科技的高速发展下,我们的CPU速度已经是非常快,我们在购买CPU的时候常常会听到4核8线程 5.2Ghz主频等等。但是相比之下我们的内存发展的就很慢。对于目前市场主流的内存来说,一般都是DDR4的对吧。就目前我知道的来说,现在内存频率最快的应该是芝奇的皇家戟,它的频率可以高达3200MHz。虽然已经很快了,但是仍然达不到我们想要的速度,这时候CPU的高速缓存作用就出来了。CPU的高速缓存集成在CPU上,一般我们都会听说缓存1、缓存2、缓存3对吧。那么CPU的高速缓存和我们内存之间有什么关联呢?请看图
手撕面试题之volatile详解(一)
通过上图,我们可以看到。实际上CPU和内存的数据交互是通过一个叫BUS总线的东西来进行的。虽然在我们肉眼上来看他们都位于主板上,可以对于计算机来说,它任然是很远的距离。于是这才有了我们的CPU高速缓存。

CPU的高速缓存

假如说,我这里有一个共享变量x=0位于主内存中,那么我们的CPU是怎么取的呢?

  • 1.CPU会先查高速缓存,高速缓存没有之后再去主内存取。
  • 2.当从主内存中找到这个变量之后会先在每个高速缓存里保存一个变量的副本,最后再由CPU去使用。

手撕面试题之volatile详解(一)
由上图我们可以看出,变量x=0在三个缓存里面都存放了一次。
那么对于我们的缓存来说我们通过上图可以看出:

  • cache1有两个部分一个部分是用来存放数据,一个是用来存放指令。它对于我们的物理核实独享的,对于逻辑核来说是独占的
  • cache2对于物理核来说是独占的,对于逻辑核来说是共享的
  • cache3对于物理核和逻辑核都是共享的。但是他是CPU独占的。

时间局部性和空间局部性

对于我们程序而言,我们的代码都是一行一行的去执行对吧。那么CPU也是这么认为的,那么它对缓存里的数据做出了两种原则。

  • 时间局部性原则:
    何为时间局部性原则?时间局部性原则就是CPU认为你刚使用的一个数据,在接下来的一段时间里你很可能再次使用这个数据,那么它就不会将你的数据从高速缓存里立马清除掉
  • 空间局部性原则:
    空间局部性原则就是说,假设我刚刚从主内存中读取了x=0这个数据,那么他认为位于它附近的数据你也很可能会使用,所以他不会只读取x=0,而是会通过缓存行的形式去读取数据。
    对于缓存行而言,一个缓存行可以存放64Byte的数据。也就是8个long、16个int、64个byte。那如果我要是一个数据不止64字节咋办?它会通过多个缓存行去存放你这个数据。

在了解了以上信息之后,我们就可以开始讲解JMM了。

何为JMM?

JMM它是一个抽象的概念,它是对多线程工作的一个规范,
每个线程启动时候在JMM里每个线程都会有一个工作内存。
如下图所示:
手撕面试题之volatile详解(一)
由上图我们可以看到,当主内存里有一个变量x=0的时候,有两个线程去读取这个变量,那么在这两个线程的工作内存中分别会存放一个x=0的变量。当我们修改一个变量的时候它会先更改工作内存中的变量,然后再同步回我们的主内存当中。

数据同步的八大原子操作

  • 1:lock :加锁,当前数据只有一个线程可以访问
  • 2:unlock:使用完数据之后进行一个解锁操作。
  • 3:read:去主内存把变量从主内存传输到内存总线中。
  • 4:load:把read操作的变量加载到工作内存中
  • 5:use:把工作内存中的变量传递给执行引擎
  • 6:assign:线程执行完毕之后,同步回工作内存
  • 7:store:把工作内存的变量写出来
  • 8:write:把store的变量传输到主内存变量中

下面我将用画图的形式来解释一个3-8的操作。1-2的操作我们放到下一章去讲。

首先假设说我有一个x=0的变量位于主内存中
手撕面试题之volatile详解(一)

假设说我现在的要去取一个数据,那么它会执行read操作,读取到bus总线中
手撕面试题之volatile详解(一)
当读取到BUS总线中之后它会执行load操作,将x=0加载到我们的工作内存当中
手撕面试题之volatile详解(一)
假如我们现在有一个操作叫x++,那么它会通过use操作把变量x=0传递给执行引擎,执行引擎在进行x=x+1的操作
手撕面试题之volatile详解(一)
那么,现在x的值应该是二了对吧,但是他不会立马同步回主内存,而是通过assign操作先把变量赋值到工作内存中
手撕面试题之volatile详解(一)
那么这时候我们工作内存中的值就变为了1。然后它会再从工作内存写到BUS总线。注意,这里任然不会写回到主内存中。原因我们下一章会讲到。
手撕面试题之volatile详解(一)
最后,它才会执行write操作写会到主内存中
手撕面试题之volatile详解(一)

好啦,今天的文章先到这里。上面的东西大家看不懂没关系,在下一章里我们讲到volatile关键字底层的一些东西大家就懂了!