从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() 方法可以安全地对计数器进行递增操作。
相关推荐
吴声子夜歌9 分钟前
Java——显示条件
java·开发语言
AC赳赳老秦14 分钟前
OpenClaw与WPS宏联动:批量执行WPS复杂操作,解决办公表格批量处理难题
java·前端·数据库·自动化·需求分析·deepseek·openclaw
ze^027 分钟前
Day01 Web应用&架构搭建&域名源码&站库分离&MVC模型&解析受限&对应路径
安全·web安全·架构·mvc·安全架构
bupt_0136 分钟前
Hermes深入理解及源码解析(二):Hermes的记忆机制
java·服务器·前端
Ting-yu1 小时前
Spring AI Alibaba零基础速成(1) ---- 项目创建与配置
java·人工智能·spring
喜欢coding的谢同学1 小时前
ArthasClaw:用自然语言诊断 JVM 的 AI 助手,告别繁琐的 Arthas 命令
java·人工智能·arthas
刀法如飞1 小时前
Palantir Ontology 数据结构分析,与ER/OOP/DDD有什么区别?
人工智能·算法·架构
swipe1 小时前
Agentic RAG:用 LangGraph 构建会路由、会纠错、会收敛的闭环 RAG
后端·langchain·llm
狼与自由1 小时前
微服务网关演化
微服务·云原生·架构
架构源启1 小时前
Spring AI完整学习路线:从Java开发到AI Agent的进阶之路(附15篇实战教程)
java·人工智能·spring