并发编程的三座大山:看不见、拆得开、乱顺序!解决它们,你就是并发大师!
一、可见性(Visibility)👁️
问题:一个线程的修改,另一个线程看不到
public class VisibilityProblem {
private static boolean flag = false; // ❌ 没有volatile
public static void main(String[] args) throws InterruptedException {
// 线程1:修改flag
new Thread(() -> {
Thread.sleep(100);
flag = true;
System.out.println("flag已设置为true");
}).start();
// 线程2:读取flag
new Thread(() -> {
while (!flag) {
// 可能永远循环!看不到flag=true
}
System.out.println("检测到flag=true");
}).start();
}
}
解决方案
方案1:volatile✅
private static volatile boolean flag = false; // ✅ 保证可见性
方案2:synchronized✅
public synchronized void setFlag(boolean value) {
flag = value; // 退出synchronized时刷新到主内存
}
public synchronized boolean getFlag() {
return flag; // 进入synchronized时从主内存读取
}
方案3:Atomic类✅
private static AtomicBoolean flag = new AtomicBoolean(false);
方案4:final✅
private final int value = 42; // final变量天然可见
二、原子性(Atomicity)⚛️
问题:操作被打断,数据不一致
public class AtomicityProblem {
private static int count = 0; // ❌ 不是原子的
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count++; // 💣 三个操作:读、加、写
}
});
threads[i].start();
}
for (Thread t : threads) t.join();
System.out.println("count = " + count); // 预期100000,实际<100000
}
}
解决方案
方案1:synchronized✅
public synchronized void increment() {
count++; // 整个方法原子执行
}
方案2:Lock✅
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
方案3:Atomic类✅(推荐)
private static AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作
三、有序性(Ordering)🔀
问题:指令重排序导致意外结果
public class OrderingProblem {
private static int a = 0;
private static boolean flag = false;
// 线程1
public static void writer() {
a = 1; // ① 可能和②重排序
flag = true; // ②
}
// 线程2
public static void reader() {
if (flag) { // ③
int i = a; // ④ 可能读到a=0!
}
}
}
解决方案
方案1:volatile✅
private static int a = 0;
private static volatile boolean flag = false; // ✅ 禁止重排序
volatile保证:
- ①不会重排到②之后
- ③不会重排到④之后
方案2:synchronized✅
public synchronized void writer() {
a = 1;
flag = true; // happens-before规则
}
public synchronized void reader() {
if (flag) {
int i = a; // 一定能看到a=1
}
}
方案3:happens-before✅
- 程序顺序规则
- volatile规则
- 锁规则
- 线程启动规则
- ...
四、三性对比表📊
| 特性 | volatile | synchronized | Lock | Atomic |
|---|---|---|---|---|
| 可见性 | ✅ | ✅ | ✅ | ✅ |
| 原子性 | ❌ | ✅ | ✅ | ✅ |
| 有序性 | ✅ | ✅ | ✅ | ✅ |
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 可重入 | - | ✅ | ✅ | - |
| 阻塞 | ❌ | ✅ | ✅ | ❌ |
五、实战案例:单例模式
❌ 错误版本(有序性问题)
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 💣 可能重排序
}
}
}
return instance;
}
}
✅ 正确版本(volatile)
public class Singleton {
private static volatile Singleton instance; // ✅ 禁止重排序
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
六、选择指南🎯
只需要可见性
// 用volatile
private volatile boolean running = true;
需要原子性
// 单个变量:Atomic
private AtomicInteger count = new AtomicInteger(0);
// 多个变量:synchronized或Lock
public synchronized void transfer(Account from, Account to, int amount) {
from.balance -= amount;
to.balance += amount;
}
需要有序性
// volatile或synchronized
private volatile boolean initialized = false;
private Data data;
public void init() {
data = new Data();
initialized = true; // volatile保证有序
}
七、面试高频问答💯
Q: volatile能保证原子性吗?
A: 不能! 只保证可见性和有序性。count++等复合操作不是原子的。
Q: synchronized保证三性吗?
A: 全保证! 可见性、原子性、有序性都保证。
Q: 什么时候用volatile?
A:
- 状态标志(boolean flag)
- 一写多读场景
- 双重检查锁定
Q: 为什么不都用synchronized?
A: 性能差,volatile更轻量。
下一篇→ ReentrantReadWriteLock的锁降级🔽