从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() 方法可以安全地对计数器进行递增操作。
相关推荐
破烂公司一级特派员34 分钟前
互联网大厂Java面试实录:从基础到微服务的深度考察
java·spring boot·微服务·八股文·面试技巧
Linux运维老纪37 分钟前
微服务6大拆分原则
java·运维·微服务
黄俊懿37 分钟前
【深入理解SpringCloud微服务】手写实现一个微服务分布式事务组件
java·分布式·后端·spring·spring cloud·微服务·架构师
在未来等你41 分钟前
互联网大厂Java求职面试:基于RAG的智能问答系统设计与实现-2
java·智能问答·milvus·向量数据库·rag·spring ai
lcw_lance1 小时前
TOGAF 企业架构介绍(4A架构)
架构·系统架构·数据库架构
Themberfue1 小时前
RabbitMQ ②-工作模式
开发语言·分布式·后端·rabbitmq
Jia ming1 小时前
Linux 内核对 ARM 大小核架构的支持
linux·arm开发·架构
极小狐1 小时前
如何使用极狐GitLab 软件包仓库功能托管 helm chart?
java·linux·服务器·数据库·c#·gitlab·maven
有梦想的攻城狮1 小时前
spring中的@Inject注解详情
java·后端·spring·inject
曼岛_1 小时前
[架构之美]Spring Boot多环境5种方案实现Dev/Test/Prod环境隔离
spring boot·后端·架构