volatile的特性
- 可见性。对一个
volatile
变量的读,总是能看到(任意线程)对这个volatile
变量最后的写入。 - 原子性。对任意单个
volatile
变量的读/写具有原子性,但类似于volatile++
这种复合操作不具有原子性。
volatile写的内存语义
当写一个volatile
变量时,JMM(Java内存模型)会把该线程对应的本地内存中的共享变量值刷新到主内存。
如图,线程A在写flag变量后,本地内存A中被线程A更新过的两个共享变量的值被刷新到主内存中。此时,本地内存A和主内存中的共享变量的值是一致的。
volatile读的内存语义
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
如图,在读flag变量后,本地内存B保护的值已经被置为无效。此时,线程B必须从主内存中读取共享变量。线程B的读取操作将导致本地内存B与主内存中的共享变量的值边城一致。
volatile写和volatile读的内存语义总结
从JDK5开始,volatile变量的写-读可以实现线程之间的通信。
- 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
- 线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。
- 线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。
volatile内存语义的实现
禁止重排序
- 当第二个操作是volatile写时,不管第一个操作时什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器排序到volatile写之后。
- 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。
- 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
比synchronized更轻量级的同步锁
在访问volatile变量时不会执行加锁操作,因此也就不会执行线程阻塞,所以volatile变量时一种比synchronized关键字更轻量级的同步机制。
volatile适用场景:一个变量被多个线程共享,线程直接给这个变量赋值
在某些场景下volatile可以代替synchronized,但不能完全取代。必须同时满足下面两个条件才能保证在并发环境的线程安全:
- 对变量的写操作不依赖于当前值(比如
i++
),或者说是单纯的变量赋值(boolean flag = true;
) - 该变量没有包含在具有其他变量的不变式中,即不同的volatile变量之间,不能互相依赖。只有在状态真正独立于程序内其他内容时才能使用volatile。
《Java并发编程的艺术》