首先引用两篇其他作者的文章:1.http://www.infoq.com/cn/articles/ftf-java-volatile 2.http://blog.csdn.net/hupitao/article/details/45227891
“volatile关键字能保证每个线程能读到最新的变量值”,要理解这句话首先明确cpu,寄存器,内存之间的关系。引用文中的一段话:
有volatile变量修饰的共享变量进行写操作的时候会多第二行汇编代码,通过查IA-32架构软件开发者手册可知,lock前缀的指令在多核处理器下会引发了两件事情。
- 将当前处理器缓存行的数据会写回到系统内存。
- 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。
处理器为了提高处理速度,不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存后再进行操作,但操作完之后不知道何时会写到内存,如果对声明了Volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
下面说一下n++操作:
被volatile修饰的n在这时会有线程安全问题,首先1.cpu会将n的值从内存读到缓存中,然后2.cpu读取缓存中的值,3.对n的值加1,然后4.写到缓存中,紧接着5.写到内存中。
线程A执行了1,2,3---让出cpu时间片---线程B也执行了1,2,3---线程A写到缓存中---线程B写到缓存中---线程A写到内存中(注意,此时强制重新从系统内存里把数据读到处理器缓存里)此时n的值被加了两次,但缓存中存的还是值加了1的值。 出现了线程安全问题。所以说volatile并不能保证原子性。
而操作:n=1+1,此之前缓存中n的值可能是旧的,那么被volatile修饰的n会强制写到内存中,寄存器会被强制刷新。
再次引用文章一段话:
1、一旦你完成写入,任何访问这个字段的线程将会得到最新的值。2、在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。
volatile为什么没有原子性?
明白了内存屏障(memory barrier)这个CPU指令,回到前面的JVM指令:从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失。
总结:volatile关键字只能保证get操作是读取的是最新的值,而set操作会写到内存中,会强制重新从系统内存里把数据读到处理器缓存里,并不具有原子性。