可见性
- 什么是可见性
一个线程修改共享变量后,其他线程能立即看到修改后的值的能力。
- 为什么会存在可见性问题?
- CPU 缓存架构:每个 CPU 核心有私有缓存(L1/L2),线程读写数据优先访问缓存而非主内存,修改后的数据不会立即同步到主内存或其他核心缓存
- 编译器优化:编译器可能将频繁读取的变量缓存在寄存器,导致线程无法看到内存中的最新值
- 如何解决可见性问题?
volatile 机制:写操作后强制刷新数据到主内存。读操作前使本地缓存失效,从主内存重新加载。底层通过 StoreLoad 屏障实现(如 x86 的 LOCK 前缀指令)
public class VolatileTest {
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("waiting");
while (!flag) {
// do nothing
}
System.out.println("flag is true");
}).start();
Thread.sleep(2000);
new Thread(VolatileTest::setFlag).start();
}
public static void setFlag() {
System.out.println("set flag");
flag = true;
System.out.println("flag is set");
}
}
有序性
- 什么是有序性?
程序执行的顺序符合代码编写的先后顺序。
- 为什么会存在有序性问题?
- 编译器指令重排序,为优化性能调整无关指令顺序。
- 处理器乱序执行,CPU 动态调整指令执行顺序以利用流水线。
- 内存系统重排序,写缓冲区导致写操作延迟提交,失效队列导致缓存失效延迟处理。
- 如何解决有序性问题?
内存屏障禁止重排序:volatile 写前插 StoreStore 屏障,写后插 StoreLoad 屏障,synchronized 退出时插入 StoreLoad 屏障
原子性
- 什么是原子性?
一个操作不可中断地完整执行,如同瞬时完成
- 为什么会存在原子性问题?
线程切换导致的指令交错。例如:
// i++ 的非原子操作
int i = 0;
i++ // 实际分三步:
// 1. 读i到寄存器 (read)
// 2. 寄存器+1 (add)
// 3. 写回内存 (write)
- 如何解决原子性问题?
使用synchronized或者使用原子类
public class Add {
int num = 0;
public long getTotal() {
return num;
}
public synchronized void add() {
num++;
}
}
使用原子类
public class AtomicAdd {
private final AtomicInteger num = new AtomicInteger();
public long getTotal() {
return num.get();
}
public void add() {
num.incrementAndGet();
}
}