You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程join场景下count值异常问题及volatile/AtomicInteger方案咨询

多线程环境下count值未达预期的问题解析

问题背景

我遇到一个主线程启动两个子线程的多线程问题,相关代码如下:

package com.multi;
public class App {
    private int count = 0;
    public void doWork() {
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    count++;
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    count++;
                }
            }
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Count is: " + count);
    }
    public static void main(String[] args) {
        App worker = new App();
        worker.doWork();
    }
}

书中提到该代码的count值可能在某些情况下小于20000,虽有解释但我未能完全理解——明明用try块调用thread.join()确保两个线程的循环执行完毕,却仍可能出现异常。现提出两个问题:
a. 哪些情况会导致count值小于20000?为何两个线程无法正确递增count?
b. 使用private volatile int count = 0; 或 private AtomicInteger count = 0; 能否从根本上解决该问题?


问题解答

a. 为什么count会小于20000?

这事儿的核心在于**count++根本不是一个原子操作**——你看着它是一行代码,底层会被拆成三个独立的步骤:

  1. 从内存里读出当前count的数值
  2. 把读到的值加1
  3. 把新值写回内存

当两个线程同时跑这个操作时,就会出现「抢资源」的竞态条件。给你举个具体的场景:

  • 线程1读到count=500,加1变成501,但还没来得及把新值写回内存
  • 这时候线程2也读到了count=500,同样加1变成501
  • 接着线程1把501写回内存,线程2随后也把501写回内存

你看,两个线程都执行了一次count++,但count只从500变成了501,相当于白白浪费了一次递增。这种情况在10000次循环里会发生好多次,最终结果自然就小于20000了。

至于你用的join()方法,它的作用只是让主线程等两个子线程都跑完再输出结果,但它管不了线程内部的操作冲突——join负责的是线程的「存活状态」,不负责线程里的操作是否安全。

b. volatile和AtomicInteger能否解决问题?

咱们分开说:

  • volatile不行:volatile关键字能保证变量的「可见性」(一个线程改了值,其他线程立刻能读到最新的),还能禁止指令重排序,但它解决不了原子性问题。count++依然是三个步骤,哪怕count加了volatile,两个线程还是可能同时读到同一个旧值,然后重复写回,竞态条件依然存在。
  • AtomicInteger完全可以:这是Java并发包专门为原子操作设计的类,它的incrementAndGet()方法(对应咱们的count++)是原子性的。底层用CAS(比较并交换)机制或者内部锁(不同JDK版本实现有差异)保证整个递增操作是一个不可分割的整体——同一时间只有一个线程能完成递增,绝不会出现两个线程操作导致的数值覆盖问题,从根本上解决了竞态条件。

内容的提问来源于stack exchange,提问作者Arefe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:49:04