一.内存可见性问题
什么是内存可见性问题
计算机运行的程序/代码,往往需要访问数据。这些数据往往存在于内存中。
cup使用此变量时,就会把内存中的数据先读出来,加载到cpu寄存器中,再去参与运算。
但是,关键是cpu读取内存的速度非常慢。这是相对的,读内存比读取硬盘快成千上万倍,而读取寄存器又比读取内存快了成千上万倍。而这里,cpu计算时,就是涉及到读取内存和读取寄存器的操作。
读取寄存器非常快,一旦涉及到读内存,cpu就慢下来了。为例提高效率,操作系统就可能在代码逻辑不变的情况下对代码进行优化,把本来读取内存的操作优化成读取寄存器的操作,减少了读取内存的次数,也就提高了效率。但是此时,如果有另一个线程对该数进行了修改并放到内存中,前一个线程就感知不到变化,就会出现问题。这就出现了内存可见性问题,也是线程安全问题的成因之一,明明内存已经修改了值,它却视而不见
例如下面的情况:
明明修改了isQuit的值,但线程1还在循环,说明while中读取到的值还是0,说明没有看到内存的修改,这就是内存可见性问题,是由于多线程引起的。我们来解释一下:
while(isQuit==0)这个判断分为俩步:第一步,load,就是cpu读取内存,将内存中isQuit的值记录到寄存器中;第二步,读取寄存器,判断isQuit的值是否为0(cmp)。
由于只是一个循环,短时间内就会进行大量循环,也就是会进行大量load,cmp操作。但是,此时JVM发现,load一次的时间内,就可以cmp成千上万次,而且发现前几次时,每次load和cmp的结果都是一样的,并且load有费时,所以编译器就做了大胆决定:不再进行load内存,而是直接从寄存器中取出isQuit。
编译器的优化:初心是好的,想要提高工作效率,但没想到其他线程动了手脚,而编译器却察觉不到,所以就做出了错误决定。
其实从上述分析中,我们可以大胆猜测,要是循环慢一点,就可能不会进行优化了,如下:
虽然只休眠了50毫秒,但却正常了,所以编译器到底什么时候触发优化机制,我们无法确定。
关于内存可见性,还涉及到一个JMM(Java Memory Model)Java内存模型这个概念。它时说到了主内存和工作内存,主内存就是我们常说的内存,而寄存器,缓存等统称为工作内存。
如何解决内存可见性问题
上述问题如何解决?我们怎么告诉编译器不要进行优化?
这就引出了volatile关键字
二.volatile关键字
volatile关键字修饰的变量能够保证内存可见性
它是如何保证被修饰的变量时内存可见性的呢?分两方面:一方面,代码在写入或修改ivolatile修饰的变量时,会先改写工作内存中的变量的值,然后会将改后的值从工作内存中刷新到主内存中;另一方面,在读取该变量时,会先将变量从主内存加载到寄存器中,再从寄存器中读取。
也就是说,加上volatile后,会让系统强制读写内存,而不是直接读写寄存器,虽然速度慢了,但时却保证了准确性。
volatile不能保证原子性
他只能让读取内存操作不被优化成读寄存器,而不能将代码捆绑到一起。保证原子性的操作还得是用synchronized