多线程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++根本不是一个原子操作**——你看着它是一行代码,底层会被拆成三个独立的步骤:
- 从内存里读出当前
count的数值 - 把读到的值加1
- 把新值写回内存
当两个线程同时跑这个操作时,就会出现「抢资源」的竞态条件。给你举个具体的场景:
- 线程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
相关产品推荐
相关产品推荐

