2025.6.6

多线程执行 int a = 1; int b = 2; int c = a + b; 的问题分析

你提供的代码:

int a = 1;      // 指令1
int b = 2;      // 指令2
int c = a + b;  // 指令3

单线程 环境下,这段代码的执行顺序不会影响最终结果(c 始终是 3)。
但在 多线程 环境下,如果多个线程同时执行这段代码,可能会遇到以下问题:


1. 多线程执行时的潜在问题

(1) 指令重排(Instruction Reordering)

  • 编译器或 CPU 可能会优化指令顺序,例如:
    • 先执行 int b = 2;(指令2),再执行 int a = 1;(指令1)。
    • 如果线程 A 执行 a = 1 但还没执行 b = 2,线程 B 读取 ab 时可能得到 a=1, b=0(默认值),导致 c 计算错误。

(2) 内存可见性问题(Visibility)

  • 由于 CPU 缓存(Cache)的存在,一个线程修改的变量可能不会立即被其他线程看到。
  • 例如:
    • 线程 A 执行 a = 1,但该值可能只写入 CPU 缓存,线程 B 仍然读到 a=0(初始值)。

(3) 竞态条件(Race Condition)

  • 如果多个线程同时修改 ab,最终 c 的值可能不符合预期。
  • 例如:
    • 线程 A 执行 a = 1,线程 B 同时执行 a = 5,最终 a 的值可能是 15,导致 c 计算错误。

2. 如何让这段代码线程安全?

(1) 使用 synchronized 加锁

public class Example {
    private int a, b, c;

    public synchronized void calculate() {
        a = 1;      // 指令1
        b = 2;      // 指令2
        c = a + b;  // 指令3
    }
}
  • 优点:简单直接,保证原子性和可见性。
  • 缺点:性能较差,多个线程不能并行执行。

(2) 使用 volatile 禁止指令重排

public class Example {
    private volatile int a, b, c;

    public void calculate() {
        a = 1;      // 指令1
        b = 2;      // 指令2
        c = a + b;  // 指令3
    }
}
  • 优点:比 synchronized 更轻量,防止指令重排。
  • 缺点:不能保证原子性(如果多个线程同时调用 calculate(),仍然可能出错)。

(3) 使用 AtomicInteger(无锁优化)

import java.util.concurrent.atomic.AtomicInteger;

public class Example {
    private final AtomicInteger a = new AtomicInteger();
    private final AtomicInteger b = new AtomicInteger();
    private final AtomicInteger c = new AtomicInteger();

    public void calculate() {
        a.set(1);      // 指令1
        b.set(2);      // 指令2
        c.set(a.get() + b.get());  // 指令3
    }
}
  • 优点:无锁,性能较好。
  • 缺点:仍然不能保证 ab 的修改是原子的(如果多个线程同时调用 calculate()c 可能计算错误)。

(4) 使用 ThreadLocal(线程隔离)

public class Example {
    private static final ThreadLocal<Integer> a = ThreadLocal.withInitial(() -> 0);
    private static final ThreadLocal<Integer> b = ThreadLocal.withInitial(() -> 0);
    private static final ThreadLocal<Integer> c = ThreadLocal.withInitial(() -> 0);

    public void calculate() {
        a.set(1);      // 指令1
        b.set(2);      // 指令2
        c.set(a.get() + b.get());  // 指令3
    }
}
  • 优点:每个线程有自己的变量副本,完全避免竞争。
  • 缺点:适用于线程隔离的场景,不适用于共享变量。

3. 结论

方案 适用场景 线程安全保证 性能
synchronized 需要强一致性 ✅ 原子性 + 可见性 较差
volatile 仅需可见性 ❌ 原子性,✅ 可见性 较好
AtomicInteger 无锁优化 ❌ 原子性(复合操作),✅ 可见性 较好
ThreadLocal 线程隔离 ✅ 无竞争 最佳

推荐方案

  • 如果多个线程需要共享计算 → 用 synchronizedReentrantLock 保证原子性。
  • 如果只是防止指令重排 → 用 volatile
  • 如果每个线程独立计算 → 用 ThreadLocal

最终线程安全版本(推荐 synchronized

public class Example {
    private int a, b, c;

    public synchronized void calculate() {
        a = 1;
        b = 2;
        c = a + b;
    }

    public synchronized int getC() {
        return c;
    }
}

这样,无论多少线程调用 calculate()c 的值始终是 3

posted @ 2025-06-06 23:20  258333  阅读(31)  评论(0)    收藏  举报