浅析 Java 内存模型 三
volatile 关键字
volatile 是什么
volatile 是一种同步机制,比 synchronized 或者 Lock 相关类更轻量,因为使用 volatile 并不会发生上下文切换等开销很大的行为。
如果一个变量被修饰成 volatile,那么 JVM 就知道了这个变量可能会被并发修改。
但是开销小,相对的能力也小,虽然说 volatile 是用来同步的保证线程安全的,但是 volatile 做不到 synchronized 那样的原子保护,volatile 仅在很有限的场景下才能发挥作用。
volatile 的使用场合
不适用 a++ 的场合
上述代码的结果并不是我们想象的是 20000,而是一个比它小的数。这是因为 a++ 运算不是原子性的操作。
适合场景 1:boolean flag
如果一个共享变量自始至终只被各个线程赋值,而没有其他的操作,那么就可以用 volatile 来代替 synchronized 或者代替原子变量,因为赋值自身是由原子性的,而 volatile 有保证了可见性,所以就足以保证线程安全。
适用场景 2:作为刷新之前变量的触发器
volatile 的作用:可见性、禁止重排序
volatile 的两点作用:
可见性:读一个 volatile 变量之前,需要先使用相应的本地缓存失效,这样就必须到主内存读取最新的值,写一个 volatile 属性会立即刷入到主内存。
禁止指令重排序优化:解决单例双重锁乱序问题。
volatile 与 synchronized 的关系
volatile 在这方面可以看做是轻量版的 synchronized:
如果一个共享变量自始至终只被各个线程赋值,而没有其他的操作,那么就可以用 volatile 来代替 synchronized 或者代替原子变量,因为赋值自身是有原子性的,而 volatile 有保证了可见性,所以就足以保证线程安全。
利用 volatile 解决重排序问题
volatile 总结
volatile 修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如 boolean flag;或者作为触发器,实现轻量级同步。
volatile 属性的读写操作都是无锁的,它不能代替 synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁上,所以所它是低成本的。
volatile 只能作用于属性,我们用 volatile 修饰属性,这样 compilers 就不会对这个属性做指令重排序。
volatile 提供了可见性,任何一个线程对其的修改将立马对其他线程可见。volatile 属性不会被线程缓存,始终从主存中读取。
volatile 提供了 happens-before 保证,对 volatile 变量 v 的写入先行发生于所有其他线程后续对 v 的读操作。
volatile 可以使得 long 和 double 的赋值是原子的。
对 synchronized 可见性的正确理解
synchronized 也可以达到同样的 happens-before 效果
这里关于 synchronized 有一个特别值得注意的点,我们之前可能一致认为,使用 synchronized 之后,synchronized 会帮我们建立临界区,这样在一个线程操作数据的时候,另一个线程无法进来同时操作,所以保证了线程安全。其实这是不全面的,这种说法没有考虑到可见性问题,真正完整的说法是:
synchronized 不仅防止了一个线程在操作某对象是受到其他线程的干扰,同时还保证了修改好之后,可以立即被其他线程所看到。(因为如果被其他线程看不到,那也会有线程安全问题)
synchronized 不仅保证了原子性,还保证了可见性。
synchronized 不仅让被保护的代码安全,还近朱者赤。
版权声明: 本文为 InfoQ 作者【朱华】的原创文章。
原文链接:【http://xie.infoq.cn/article/6278ff215122e90148c52d8bd】。未经作者许可,禁止转载。
评论