讲volatile关键字的作用之前,先回顾下内存和cpu中间的cache的由来和作用。
我们知道在计算机中cpu的运算速度是最快的,内存其次,但因为内存读写速度往往跟不上cpu执行指令的速度,所以在cpu和内存之间添加高速缓存cache,将内存中的数据拷贝一份副本放到cache中,cpu运算时从cache中获取缓存数据,向缓存读写数据,操作完成后刷新缓存到内存。
但是,当多个线程访问cache中的共享变量(java中通常用static修饰变量,即全局变量)时,假设有一个线程更改了它,那么其他线程并不知道这一更新,导致共享变量最终结果不是全部的更新,即缓存不一致。那么如何解决缓存的一致性问题呢?
所以就出现了缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,更新共享变量后会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
volatile实际上也实现了和缓存一致性一样的功能,在某种程度上比synchronized(一个线程内的synchronized修饰的代码块或者对变量加同步锁内的代码块只有全部执行完毕,其他代码才会执行,即其他线程或者同一线程内的代码需要等待执行)更优,更能保证高并发场景下的数据一致性问题。
volatile具备两个作用:
保证数据的修改立即强制更新到主存,使其他线程的缓存失效,并更新缓存,即保证数据对其他线程的可见性。
禁用指令重排序(jvm对java代码指令重排,但并不是完全打乱代码的执行顺序,至少能保证代码块的执行最终结果是一致的,当具有依赖时先执行被依赖的变量的初始化)
禁用指令重排序,意味着对volatile修饰变量的操作前的代码一定先执行,后的代码一定后执行,即对volatile修饰变量的操作对其后的代码都是可见的,而不会发生指令重排序颠倒顺序执行。
volatile使用条件:
必须保证对volatile修饰变量的操作具备原子性,因为一旦不具备原子性,读取变量后假设线程进入阻塞状态,变量而未进行修改时,此时线程中的缓存并未失效,而其他线程对变量进行了修改,那么就出现了数据不一致的情况。