volatile关键字简介

开始

volatile关键字,模板化概念我就不写了,网上大把。
我们还是保持本博客的传统,从最笨的理解开始说起:

  1. 先大概了解几个概念:线程、内存(了解计算机内存就行,本文不涉及JMM)
  2. 线程是干活的,干活就需要用到数据,数据存在哪里呢?需要长期保存的就做持久化(存入数据库、文件等),干活期间临时使用的数据(也就是变量),这个存在内存中。
  3. 线程是在cpu中的(进程中),线程中使用变量的时候,是从内存中读取/写入。硬件层面也就是CPU访问内存条。
  4. 虽然内存条的性能也很好,但CPU性能远远高于内存条,为了使CPU读写数据的时候尽量不被内存条性能影响,在CPU中增加了一个缓存模块,即CPU缓存。
  5. 此后,CPU的读写操作都是对CPU缓存的读写,CPU缓存中的数据会选择合适的时机去和主内存同步。如图:
    image
  6. 这样读写性能是高了,但是带来了新的问题:
    因为有了CPU缓存,每个线程有自己的工作内存,当对一个变量写入值之后,其他线程不能及时获取到新值(因为还没同步,同步后就是新值了)。这就是内存可见性问题。
  7. volatile关键字就是为了解决内存可见性问题的,被volatile修饰的变量,程序对其进行读写操作时,都是直接操作主内存的,就解决了内存可见性问题。
  8. 有人说了,那被volatile修饰的变量不就是没用到cpu缓存吗?那性能不是下降了吗?
  9. 没错,你说的是对的。性能确实下降了。

扩展

  1. 在其他文章中看到volatile是解决了两个问题:
  • 内存可见性
  • 禁止指令重排序
  1. 上面只介绍了内存可见性问题的解决,没有说禁止指令重排序啊
  2. 没错,下面说这个指令重排序,因为这个“禁止指令重排序”也是为了解决内存可见性问题的。
  3. 先说一下啥是指令重排序,指令指的是cpu指令。
  4. 比如,我们要定义一个变量并赋值,java代码是这样写:
    int num = 1;
    
    编译成cpu指令是这样的(逻辑代码),cpu指令重排序之前:
    定义一个引用 num
    申请一块内存空间,拿到空间地址
    将值1写入这块内存空间
    将这块空间地址赋值给num
    
    cpu指令重排序之后:
    定义一个引用 num
    申请一块内存空间,拿到空间地址,赋值给num
    将值1写入这块内存空间
    
  5. 大家发现问题了吗,cpu是先把内存空间的引用赋值给num,再写入真实的值1的。cpu这么做的目的是为了更快的响应(个人初步猜测,需要深入研究寻找证据或推翻猜测)。如果第二句指令执行完了,第三句还没执行的时候,我们去读取num的值,读到的是空的。
  6. volatile的禁止重排序就是为了解决上面的问题。
posted @ 2023-02-23 12:37  hucat  阅读(23)  评论(0)    收藏  举报