如何在Java中处理线程间数据可见性

使用volatile确保简单变量可见性,synchronized和Lock保证原子性与可见性,Atomic类实现无锁线程安全,合理选择取决于场景需求。

在Java中,线程间数据可见性问题源于每个线程可能拥有共享变量的本地副本(如CPU缓存),导致一个线程对变量的修改不能及时被其他线程看到。要确保数据在多线成品间正确可见,需要使用Java内存模型(JMM)提供的同步机制。

使用volatile关键字

volatile 是处理简单共享变量可见性的最轻量级方式。当一个变量被声明为 volatile,Java会保证:

  • 对该变量的写操作对后续读操作的线程立即可见
  • 禁止指令重排序优化,确保执行顺序符合预期

适合场景:状态标志位、双检锁中的实例引用等。注意 volatile 不保证复合操作的原子性(如 i++)。

示例:
private volatile boolean running = true;

public void stop() {
    running = false;
}

public void runLoop() {
    while (running) {
        // 执行任务
    }
}

使用synchronized关键字

synchronized 不仅保证代码块的原子性,还建立“happens-before”关系,从而保障可见性。

进入 synchronized 块前,线程会清空本地变量副本,从主内存重新读取;退出时,修改会被刷新回主内存。

  • 可用于方法或代码块
  • 多个线程竞争同一把锁时,能保证数据的一致读写
示例:
private int count = 0;
public synchronized void increment() {
    count++;
}
public synchronized int getCount() {
    return count;
}

使用java.util.concurrent.atomic包

Atomic类(如 AtomicInteger、AtomicBoolean)基于CAS(Compare-And-Swap)实现,既保证可见性也保证原子性。

  • 内部使用 volatile 保证可见性
  • 通过Unsafe类调用底层CPU指令实现原子更新
  • 适用于计数器、状态切换等高频读写场景
示例:
private AtomicInteger counter = new AtomicInteger(0);

public void increment() {
    counter.incrementAndGet();
}

public int getValue() {
    return counter.get();
}

使用显式内存屏障(Lock API)

ReentrantLock、ReadWriteLock 等显式锁在释放锁时会强制将修改刷回主内存,获取锁时会失效本地缓存。

它们不仅提供互斥,也建立 happens-before 关系,确保线程间的数据可见。

相比 synchronized,更灵活,支持中断、超时、公平性等特性。

基本上就这些。合理选择机制取决于具体场景:volatile 用于状态标志,synchronized 和 Lock 用于复合操作保护,Atomic 类用于无锁原子操作。关键是理解每种方式背后的内存语义。