讲到Java并发,多线程编程,一定避免不了对关键字volatile的了解,那么如何来认识volatile,从哪些方面来了解它会比较合适呢?
个人认为,既然是多线程编程,那我们在平常的学习中,工作中,大部分都接触到的就是线程安全的概念。
而线程安全就会涉及到共享变量的概念,所以首先,我们得弄清楚共享变量是什么,且处理器和内存间的数据交互机制是如何导致共享变量变得不安全。
共享变量
能够在多个线程间被多个线程都访问到的变量,我们称之为共享变量。共享变量包括所有的实例变量,静态变量和数组元素。他们都被存放在堆内存中。
处理器与内存的通信机制
大家都知道处理器是用来做计算的,且速度是非常快的,而内存是用来存储数据的,且其访问速度相比处理器来说,是慢了好几个级别的。那么当处理器需要处理数据时,如果每次都直接从内存拿数据的话,就会导致效率非常低,因此在现代计算机系统中,处理器是不直接跟内存通信的,而是在处理器和内存之间设置了多个缓存,也就是我们常常听到的L1, L2, L3等高速缓存。
具体架构如下所示:
memory_processor_communication.png
处理器都是将数据从内存读到自己内部的缓存中,然后在缓存中对数据进行修改等操作,结束后再由缓存写到回主存中去。如果一个共享变量 X,在多线程的情况下,同时被多个处理器读到各自的缓存中去,当其中一个处理器修改了X的值,改成Y了,先写回了内存,而此时另外一个处理器,又将X改成Z,再写回内存,那么之前的Y就会被覆盖掉了。
这种情况下,数据就已经有问题了,这种因为多线程操作而导致的异常问题,通常我们就叫做线程不安全。
memory_processor_communication_core1.png
memory_processor_communication_core2.png
如上述两图所示,X的变量同时被不同的处理器修改成各自的Y和Z,那么如何避免这种情况呢?这就涉及到了Java内存模型中的可见性的概念。
java思维导图领取路径
Java内存模型之可见性
可见性,意思就是说,在多线程编程中,某个共享变量在其中一个线程被修改了,其修改结果要马上能够被其他线程看到,拿上面的例子来说,也就是当X在其中一个处理器的缓存中被修改成Y了, 另一个处理器必须能够马上知道自己缓存中的X已经被修改成Y了,当此处理器要拿此变量去参与计算的时候,必须重新去内存中将此变量的值Y读到缓存中。
而一个变量,如果被声明成violate,那么其就能保证这种可见性,这就是volatile变量的作用了。
volatile
那么 volatile 变量能够保证可见性的实现原理是什么?声明成volatile的变量,在编译成汇编指令的时候,会多出以下一行:
0x0bca13ae:lock addl $0x0,(%esp) ;
这一句指令的意思是在寄存器上做一个+0的空操作,但这条指令有个Lock前缀。而处理器在处理Lock前缀指令时,其实是声言了处理器的Lock#信号。在之前的处理器中,Lock#信号会导致传输数据的总线被锁定,其他处理器都不能访问总线,从而保证处理Lock指令的处理器能够独享操作数据所在的内存区域。