0. 背景
在Java的各种面试题中,一个出现频率很高的题就是:
volatile 关键字是什么意思?作用是什么?
想必熟悉Java的朋友应该对这个关键字已经有所了解了,同时应该也有很多朋友对这个关键字略有一知半解,只是知道它是“和并发编程相关的”。
那么,本文接下来将会介绍下 volatile
1. volatile 关键字的语法含义
首先,volatile
是一个关键字,用于修饰成员变量。这个在使用时可以说是非常方便了。
从Java语法的角度来说,在Java语言规范中,是这么说明 volatile 关键字的:
The Java programming language allows threads to access shared variables (§17.1). As a rule, to ensure that shared variables are consistently and reliably updated, a thread should ensure that it has exclusive use of such variables by obtaining a lock that, conventionally, enforces mutual exclusion for those shared variables.
The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes.
A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable
这段描述,我提取了下里面的主要的内容,大概是以下几个关键点:
1) Java支持多线程访问共享变量;
2) Java可以通过给对象加锁来实现线程互斥访问,进而保证变量访问的多线程安全问题;
3) 除了互斥访问之外,还提供另一种机制,就是用 volatile
关键字来修饰变量,这个机制能在某些特点的场景中很有用;
4) 用volatile
修饰的变量,可以确保它的可见性
;
以上几个关键点,前3个都好理解,比如加锁来做线程同步等等,只有第4点,啥叫能保证可见性
?可见性
又是个啥?
2. 什么是可见性问题?
要说到可见性是什么,其实这个问题来源于CPU的缓存一致性
的问题。
现代的CPU都是有高速缓存的,而且还是有多级缓存的。为什么叫高度缓存呢?这个是相对内存来说的,高度缓存的读写速度要比内存高很多,这样设计也是为了提高CPU的速度,如果没有这个CPU内的缓存的话,那涉及到和内存读写交互的话,CPU大部分时间都是在等待着内存数据的读写。
同时,现代CPU基本都是多核的了,还有单核心上的超线程技术,这样就相当于有多个CPU同时和内存进行读取操作,每一个CPU核心里面都有各自的缓存,那么如何保证每一个读取到的数据都是新的呢?
这个就是可见性问题。
缓存一致性(来自维基百科)
上面的Client可以代表一个核心,每一个核心在从内存读取数据的时候,会先把数据复制到自己的缓存中一份,缓存一致性就是需要保证在多个里面的缓存是正确的。
正是基于以上的设计,才会有JVM中的可见性的问题。
JVM是多线程的环境,多个线程在实际运行的时候,有可能是在不同的CPU核心中运行的,所以每一个线程在读取内存的数据的时候,也有可能是读取的线程内部的缓存,这个时候,假如其他某个线程修改了这个变量,对于这线程来说是不知情的,也就是不可见的
。
多个线程对数据读写
3. JMM中关于volatile的可见性保证
JVM为了解决上面的可见性问题,在规范层面进行了约束,新增了 volatile 关键字的机制来解决这个问题。它能保证,某一个线程对变量修改了以后,对其他的线程时可见的。
这样的设计,针对一写多读
的并发环境,是非常有帮助的。由于只有一个线程在写变量,加上锁的话会对性能影响比较大。如果直接把共享变量声明成为 volatile
的就可以实现可见性了。
volatile 的机制,只是规范层的约束,那么底层的实现呢?这个实现的话,就是基于底层的CPU的MESI协议,这个协议就是专门用于解决CPU的多个核心之间的缓存的一致性问题的,有了它就可以做到啦,这里不再展开了。
参考资料: