volatile 探秘


简介

volatile 这个单词一定要能够拼写正确 v-o-l-a-t-i-l-e ,因为我相信你在写代码的时候肯定没有经常用到它。还有一个最长的关键字叫 synchronized 。

volatile 的中文翻译

{
  "translation": [
    "挥发性"
  ],
  "basic": {
    "us-phonetic": "ˈvɑːlətl",
    "phonetic": "ˈvɒlətaɪl",
    "uk-phonetic": "ˈvɒlətaɪl",
    "explains": [
      "n. 挥发物;有翅的动物",
      "adj. [化学] 挥发性的;不稳定的;爆炸性的;反复无常的",
      "n. (Volatile)人名;(意)沃拉蒂莱"
    ]
  }
}

可以看到是挥发性,也可以理解为易失性。容易失去它,为什么这么说呢?因为被volatile修饰的变量在多线程或者多核cpu中,只要一个线程或cpu修改了它的值,那在其他线程或cpu里面就失去了它,如果还想获取到它,就不得不去指定的内存地址里面在去找它。
所以这个字段修饰的变量一定要被多线程访问才行,否则修饰它就没有意义了。编译器在编译的时候就有可能帮你吧这个修饰符自动给去掉。所以单线程里面我们一般不用volatile关键字修饰变量,因为你修饰了也没用。由此建议写代码的时候审视一下是不是该用volatile关键字修饰,关键在于这个变量是不是多线程共享的,另外就算是多线程共享的也不一定要被volatile修饰,例如引用变量,只要引用变量的初始化不是在多个线程里面都写一份就不需要修饰了。

怎么实现

不整那些虚头巴脑的名词,什么 内存可见性啊、缓存行啊、嗅探啊、读屏障、写屏障啊、lock指令啊。我直接说过程。
首先被 volatile 修饰的关键字在cpu里面或者某个线程里面被修改之前,先发送一个命令给其他cpu,大喊一声我要修改xxx内存地址的值了,你们谁有不要在动这个地址了,这一声大喊,其实就是在一个电子电路上加了一个高电平,这个电平加的及其快,快到几乎是所有cpu同时收到,然后其他cpu就会把这个内存对应的cpu里面的缓存设置为失效状态,当然如果在设置失效之前,你读取了这个缓存值去处理我们就没有办法了,因为我们没有办法让时光倒流嘛。但是只要刚刚那个cpu说了我要修改,其他cpu再想读写这个内存里面的值就读不出来了,就会阻塞,但是阻塞的机器很短,因此cpu会选用自旋的方式,哎呀所谓自旋就是不停的看我能不能不了,我能不能读了,如果不能就继续疯狂的询问,为的是减少上下文的切换的开销。
然后大吼一声的cpu开始计算修改缓存里面的值,完事之后有一个最重要的一步那就是把这个内存里面的值,立马写回到共享内存中。这之后,在大吼一声,你们可以操作该内存地址啦。

这个开始大吼一声的操作总要有一个指令来处理吗,对应的就是lock指令。lock指令就是加了读写屏障嘛,就是让你不能读也不能写码
这个让其他cpu缓存失效的其实就是设置的缓存行的状态嘛
这个设置缓存行失效的方式我么就叫做嗅探技术嘛。

这里唠叨一下嗅探技术,网上没有搜到任何关于嗅探技术的文章,都是一笔带过,这里我谈谈我的理解,但不保证是对的啊。
这个理cpu的嗅探并不是,其中一个cpu大喊我要修改xx内存的东西了,之后 ,所有的cpu就设置自己对应的缓存失效的。而是真真正正cpu修改了内存的值之后才生效的,怎么做的呢,你可以理解为cpu 在 内存和缓存之间画了一条连接线,这个连接线要么都是高电平,要么都是低电平,我们知道一条电线只要中间没有电阻,两端电压其实是一样的,只要修改了内存的值,那么就加高电平,cpu先检测这个连线是不是高电平。如果是就重新读内存值。由此可见,嗅探一定是自己去嗅探,主动发现。这就是我的理解,不服来战。


评论
  目录