一、背景
我们在写项目的时候,有时会使用多线程。为了保证一部分线程之间的通信,所以需要线程中的一些变量具有可见性。
说到线程可见性,对于Java而言,有两种方法实现:volatile和synchronized。
需要注意的是:volatile只用来保证该变量对所有线程的可见性,但不保证原子性。
虽然加锁同样能解决共享变量不可见性的问题,但是加锁和锁的释放过程都是会有性能消耗的,所以在解决共享变量不可见性的问题时,我们首选 volatile关键字
二、作用
volatile是Java提供的轻量级的同步机制,保证了可见性,不保证原子性。要了解volatile工作机制,首先要对Java内存模型(JMM)有初步的认识:
- 每个线程创建时,JVM会为其创建一份私有的工作内存(栈空间),不同线程的工作内存之间不能直接互相访问。JMM规定所有的变量都存在主内存,主内存是共享内存区域,所有线程都可以访问
- 线程对变量进行读写,会从主内存拷贝一份副本到自己的工作内存,操作完毕后刷新到主内存。所以,线程间的通信要通过主内存来实现。
volatile的作用是:线程对副本变量进行修改后,其他线程能够立刻同步刷新最新的数值,这个就是可见性。同时还有禁止指令重排。
三、原理
volatile的内存语义
内存语义可以理解为 volatile 在执行计算时,内存中的要实现的功能与规则:
- 写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。
- 读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效,并从主内存中读取共享变量。
四、volatile 与 static
- static 修饰的变量:多实例间,保证变量的唯一性。但是没有可见性和原子性的保证。
- volatile 修饰的变量:多实例间,变量没有唯一性。但是能保证线程可见性,不保证原子性。
所以,static volatile 修饰的变量就是多实例间的唯一性,以及线程间的可见性。
五、总结
- 适用场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如 booleanflag;或者作为触发器,实现轻量级同步。
- volatile 变量的读写操作都是无锁的,低成本的。它不能替代 synchronized,因为它没有提供原子性和互斥性。
- volatile 只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序。
- volatile 可以在单例双重检查中(特殊场景)实现可见性和禁止指令重排序,从而保证安全性。