从CPU角度来看并发问题

Java并发-从CPU角度来看并发问题的本质

一个令人困惑的并发问题

先看这段模拟并发问题的代码:

java 复制代码
public class ConcurrencyExample {
    private static int counter = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter++; // 非原子操作
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) { t.join(); }
        System.out.println("预期值: 1000000, 实际值: " + counter);
    }
}

运行结果总是小于1000000,这个现象揭示了并发问题的本质。要理解这个问题,我们需要深入CPU架构层面。

CPU视角下的三大并发问题

1. 缓存金字塔带来的可见性问题

现代CPU采用分级缓存架构:

  • L1/L2缓存(核心独享)
  • L3缓存(多核共享)
  • 主内存

当线程A修改核心1缓存中的数据时,线程B在核心2缓存中看到的仍是旧值。这种内存可见性问题导致不同线程看到的数据状态不一致。

2. 流水线优化导致的原子性问题

counter++在机器指令层面需要三个步骤:

  1. 从内存加载counter值到寄存器
  2. 对寄存器执行+1操作
  3. 将新值写回内存

当两个线程同时执行这三个步骤时,可能出现以下交错执行情况:

ini 复制代码
Thread1: 读取counter=100
Thread2: 读取counter=100
Thread1: 计算101,写入内存
Thread2: 计算101,写入内存

最终结果只增加了1次而不是2次。

3. 指令重排序引发的有序性问题

现代CPU会进行指令重排序优化,考虑以下代码:

java 复制代码
int a = 1;
boolean flag = false;

// 线程1
void write() {
    a = 2;          // 语句1
    flag = true;    // 语句2
}

// 线程2
void read() {
    if(flag) {      // 语句3
        assert a==2; // 语句4
    }
}

CPU可能将语句1和2重新排序,导致线程2看到flag=true时a仍然为1。

Java内存模型(JMM)的应对策略

内存屏障(Memory Barrier)

通过插入特定指令实现:

  • LoadLoad屏障
  • StoreStore屏障
  • LoadStore屏障
  • StoreLoad屏障

保证屏障前后的指令不会被重排序。

volatile关键字

通过内存屏障实现两大特性:

  1. 可见性:写操作立即刷新到主内存
  2. 有序性:禁止指令重排序

CAS原子指令

Compare-And-Swap的CPU指令实现无锁并发:

java 复制代码
AtomicInteger atomicCounter = new AtomicInteger(0);

void safeIncrement() {
    atomicCounter.incrementAndGet();
}

其底层实现依赖CPU的LOCK CMPXCHG指令。

最佳实践

  1. 优先使用java.util.concurrent包中的原子类
  2. 明确共享变量的访问边界
  3. 对复合操作使用synchronized
  4. 谨慎使用volatile保证可见性

优化后数据正确

counter++ 并非原子操作。这意味着在多线程环境下,多个线程有可能同时访问并修改 counter 变量,从而产生数据竞争与不一致的结果。要解决这个问题,需保证 counter 变量的修改是线程安全的。

修改方案

有两种常用的解决办法:

  1. 使用 synchronized 关键字:此关键字可确保在同一时间只有一个线程能够执行同步块中的代码,从而避免数据竞争。
  2. 使用 AtomicInteger 类:该类提供了原子性的操作方法,能够在多线程环境下安全地对整数进行操作。

修改后的代码

使用 synchronized 关键字

ini 复制代码
public class ConcurrencyExample {
    private static int counter = 0;
    // 定义一个静态对象作为同步锁
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < threads.length;  i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    // 同步块,保证同一时间只有一个线程能执行
                    synchronized (lock) {
                        counter++;
                    }
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }
        System.out.println(" 预期值: 1000000, 实际值: " + counter);
    }
}

使用 AtomicInteger

ini 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class ConcurrencyExample {
    // 使用 AtomicInteger 类代替普通的 int 类型
    private static AtomicInteger counter = new AtomicInteger(0); 

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    // 使用原子操作递增计数器
                    counter.incrementAndGet(); 
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }
        System.out.println("预期值: 1000000, 实际值: " + counter.get());
    }
}
  1. synchronized 关键字:synchronized (lock) 语句块保证了同一时刻只有一个线程能够执行 counter++ 操作,避免了多个线程同时修改 counter 变量的问题。
  2. AtomicInteger 类:AtomicInteger 类内部使用了 CAS(Compare-And-Swap)机制来保证操作的原子性,incrementAndGet() 方法可以安全地对计数器进行递增操作。
相关推荐
观无2 分钟前
基于AOP+Log4Net+AutoFac日志框架
java·大数据·数据库
AronTing2 分钟前
09-RocketMQ 深度解析:从原理到实战,构建可靠消息驱动微服务
后端·面试·架构
方块海绵4 分钟前
RabbitMQ总结
后端
星辰大海的精灵5 分钟前
Python 中利用算法优化性能的方法
后端·python
雷渊6 分钟前
深度分析Scroll API(滚动搜索)方案
后端
AronTing6 分钟前
11-Spring Cloud OpenFeign 深度解析:从基础概念到对比实战
后端·spring cloud·架构
yifuweigan6 分钟前
J2Cache 实现多级缓存
后端
洛神灬殇9 分钟前
【Redis技术进阶之路】「原理分析系列开篇」探索事件驱动枚型与数据特久化原理实现(时间事件驱动执行控制)
redis·后端
Java中文社群11 分钟前
SpringAI版本更新:向量数据库不可用的解决方案!
java·人工智能·后端
日月星辰Ace13 分钟前
蓝绿部署
运维·后端