并发三性:可见性、原子性、有序性🔺

49 阅读3分钟

并发编程的三座大山:看不见、拆得开、乱顺序!解决它们,你就是并发大师!

一、可见性(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规则
  • 锁规则
  • 线程启动规则
  • ...

四、三性对比表📊

特性volatilesynchronizedLockAtomic
可见性
原子性
有序性
性能⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
可重入--
阻塞

五、实战案例:单例模式

❌ 错误版本(有序性问题)

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:

  1. 状态标志(boolean flag)
  2. 一写多读场景
  3. 双重检查锁定

Q: 为什么不都用synchronized?

A: 性能差,volatile更轻量。


下一篇→ ReentrantReadWriteLock的锁降级🔽