- volatile关键字是JVM提供最轻量级的同步机制!!!
- volatile关键字具备两种特性:
- 保证变量对所有线程的可见性
- 禁止指令重排序优化
- 千万注意:因为java中的运算不具备原子性,不能保证线程安全,不能保证线程安全。
- 可见性
- 当一个线程修改了变量的值,新值对于其他线程是可以立即得知的。
- 在各个线程的工作内存中,volatile变量也可以存在不一致的情况,但由于每次使用变量前都要先从主内存刷新,JVM的执行引擎看不到不一致的情况,因此可以认为不存在一致性问题。
- 一般来说,在以下两条规则下使用volatile才可以保证线程是安全的:
- 运算结果并不依赖变量的当前值或者能保证只有单线程修改变量的值
- 变量不需要与其他的状态变量共同参与不变的约束(暂时不是很理解???)
- 禁止指令重排序
- 正常普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方能获取到正常的结果,不能保证变量赋值的操作和程序代码中的执行顺序一致
int a = 1;
int b = 2;
int c = 3;
Q:那么volatile关键字是如何保证“可见性”和“指令重排序的”?
- 被volatile修饰的变量,在被赋值后,汇编代码会多执行一个“lock addl $0x0,(%esp)”操作,该指令相当于一个“内存屏障”(它不允许屏障后面的指令被重排序到屏障前)。“lock addl $0x0,(%esp)”的语义是把ESP寄存器的值加“0”,是一个空操作。关键在于lock这个前缀,IA32手册(是因特尔32位体系的一个架构,不是很懂)中,它的作用是使得本CPU的Cache写入了内存时,该写入动作也会引起别的CPU或者别的内核无效化其Cache。这相当于Cache中的变量做了一次“store和write”操作。通过这样一个空操作,让volatile变量的修改对其他CPU立即可见。
- 而“禁止指令重排序”,指令重排序,并不是说指令可以任意重排,CPU需要能够正确处理 “依赖情况” 以保证程序能够得到正确的结果。而“lock addl $0x0,(%esp)”会把修改同步到主内存中,意味着之前的操作均已完成(相当于前后产生了“依赖”),这样就筑起了“内存屏障”。而volatile变量之前的指令个人理解还是可以重排序,但不能排到volatile变量赋值动作的后面去,因为volatile变量之前的指令重排序并不会影响volatile变量的结果正确性。
Volatile基本原则及选择依据
- volatile变量的读操作与普通变量基本没有查询
- volatile变量的写操作与普通变量稍微慢一些(毕竟要多做一些动作保证可见性和禁止指令重排序)
- 选择依据:volatile关键字的语义是否能够满足你的使用场景
Java内存模型的基本操作对Volatile变量的规则限制
- 一个线程对volatile变量进行use操作时,必须先进行read和load操作(保证值是从主内存拿的且是最新的)
- 一个线程对volatile变量进行了assign操作,必须进行store和write操作(保证主内存中值是最新的)