Version: Next

Volatile

Volatiole是JVM提供的轻量级synchronized同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

保证可见性

上一章的代码中,给num加上volatile

public class Demo {
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException { // 主线程
new Thread(() -> { // 线程1
while (num == 0) {
}
}).start();
// 睡2秒,让上面自定义的线程先跑起来
TimeUnit.SECONDS.sleep(2);
// 在主线程修改num的值
num = 1;
// 输出num
System.out.println(num);
}
}

程序正常退出了


不保证原子性

原子性:不可分割

class Demo2 {
private static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield(); // 只要有main 和 gc之外的线程,主线程就礼让
}
System.out.println(Thread.currentThread().getName() + " | " + num);
}
}
main | 19269

给num加上volatile

class Demo2 {
private volatile static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield(); // 只要有main 和 gc之外的线程,主线程就礼让
}
System.out.println(Thread.currentThread().getName() + " | " + num);
}
}
main | 18573

依然不等于20000,volatile不保证原子性

如何不使用Lock或synchronized实现原子性?

AtomicInteger原子类,还有其他各种原子类

class Demo2 {
private static AtomicInteger num = new AtomicInteger();
public static void add() {
num.getAndIncrement(); // AtomicInteger + 1
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield(); // 只要有main 和 gc之外的线程,主线程就礼让
}
System.out.println(Thread.currentThread().getName() + " | " + num);
}
}
main | 20000

为什么原子类能够保证原子性,效率还高?

CAS

  • 这些类的底层都是native方法,直接和操作系统挂钩,效率极高,直接在内存中修改值
  • Unsafe

禁止指令重排

什么是指令重排

计算机并不是按照程序员写的程序的顺序执行的

  1. source code
  2. 编译器优化重排
  3. 指令并行可能重排
  4. 内存系统重排
  5. 执行
int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4

程序员期望的执行顺序:1234

可能的真实执行顺序:2134、1324

不可能的执行顺序:4123

处理器在进行指令重拍时,会考虑数据之间的依赖性

  • 可能造成影响的结果 假设abxy默认值都为0
线程A线程B
x = ay = b
b = 1a = 2

正常结果 x =0 , y = 0 ;

可能被重排为:

线程A线程B
b = 1a = 2
x = ay = b

结果为: x = 2; y = 1

Volatile禁止指令重排

  • 只要加了Volatile就会禁用指令重排
  • Volatile通过内存屏障来阻断常规的指令重拍,确保数据的可见性

内存屏障

关于内存屏障的详解 -> 内存屏障及其在JVM内存管理中的应用

  • 缓存一致性协议 MESI
  • 指令重排、指令乱序
  • StoreBuffer与Invalid Queue
  • 缓存级屏障

内存屏障

  • LoadLoad Barriers
  • StoreStore Barriers
  • LoadStore Barriers
  • StoreLoad Barriers(重要、也最重)

image-20200701153806023

image-20200701154025101

image-20200701155059126

image-20200701155137125

最后,不同品牌、厂商,不同架构的CPU内存屏障实现机理不同,JMM为我们屏蔽了这些跨平台的区别