1、volatile的特性
理解volatile
特性的一个好办法是把对volatile
变量的单个读/写,看成是使用同一个锁对单个读/写操作做了同步。
代码示例:
package com.lizba.p1; /** * <p> * volatile示例 * </p> * * @Author: Liziba * @Date: 2021<b>本文来源gao@!dai!ma.com搞$$代^@码5网@</b>/6/9 21:34 */ public class VolatileFeatureExample { /** 使用volatile声明64位的long型变量 */ volatile long v1 = 0l; /** * 单个volatile写操作 * @param l */ public void set(long l) { v1 = l; } /** * 复合(多个)volatile读&写 */ public void getAndIncrement() { v1++; } /** * 单个volatile变量的读 * @return */ public long get() { return v1; } }
假设有多个线程分别调用上面程序的3个方法,这个程序在语义上和下面程序等价。
package com.lizba.p1; /** * <p> * synchronized等价示例 * </p> * * @Author: Liziba * @Date: 2021/6/9 21:46 */ public class SynFeatureExample { /** 定义一个64位长度的普通变量 */ long v1 = 0L; /** * 使用同步锁对v1变量进行写操作 * @param l */ public synchronized void set(long l) { v1 = l; } /** * 通过同步读和同步写方法对v1进行+1操作 */ public void getAndIncrement() { long temp = get(); // v1加一 temp += 1L; set(temp); } /** * 使用同步锁对v1进行读操作 * @return */ public synchronized long get() { return v1; } }
如上两个程序所示,一个volatile
变量的单个读\写操作,与一个普通变量的读\写操作都是使用同一个锁来同步,它们之间的执行效果相同。
上述代码总结:
锁的happens-before
规则保证释放锁和获取锁的两个线程之间的内存可见性,这意味着对一个volatile
变量的读,总能看到(任意线程)对这个volatile
变量最后的写入。
锁的语义决定了临界区代码的执行具有原子性。这意味着,即使是64位的long型和double
型变量,只要它是volatile变量,对该变量的读/写就具有原子性。如果是多个volatile
操作或类似于volatile++
这种复合操作,这些操作整体上不具备原子性。
总结volatile特性:
- 可见性。对一个
volatile
变量的读,总是能看到(任意线程)对这个volatile
变量最后的写入。 - 原子性。对任意
volatile
变量的读/写具有原子性,但类似volatile++
这种复合操作不具有原子性。
2、volatile写-读建立的happens-before关系
- 对于程序员来说,我们更加需要关注的是volatile对线程内存的可见性。
从JDK1.5(JSR-133)开始,volatile
变量的写-读可以实现线程之间的通信。从内存语义的角度来说,volatile
的写-读与锁的释放-获取有相同的内存效果。
volatile
的写和锁的释放有相同的内存语义volatile
的读和锁的获取有相同的内存语义
代码示例:
package com.lizba.p1; /** * <p> * * </p> * * @Author: Liziba * @Date: 2021/6/9 22:23 */ public class VolatileExample { int a = 0; volatile boolean flag = false; public void writer() { a = 1; // 1 flag = true; // 2 } public void reader() { if (flag) { // 3 int i = a; // 4 System.out.println(i); } } }