Java多线程求和ArrayList触发ConcurrentModificationException求助
问题分析与解决方案
嗨,我来帮你捋一捋你遇到的问题哈~你现在碰到两个核心问题:ConcurrentModificationException异常和求和结果不符合预期,咱们一个个拆解清楚:
为什么会出现ConcurrentModificationException?
你给populate()和sum()加了synchronized,但这俩是实例方法,锁的是当前Tst对象本身。而你在main方法里new了三个完全不同的Tst实例,这意味着三个线程各自拿着不同的锁,完全起不到互斥的作用!
当其中一个线程在调用sum()遍历list的时候,另外的线程可能还在调用populate()往list里加元素——而ArrayList本身不是线程安全的容器,在遍历过程中如果有其他线程修改它的结构(add/remove等),就会触发这个并发修改异常。
为什么求和结果不对?
静态变量sum被三个线程同时修改,而且sum+=ger这个操作不是原子性的(会拆分成读-加-写三步),多个线程同时操作会导致数据覆盖,所以最终结果会比预期的500500小。
修正后的代码
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; public class Tst extends Thread { // 用AtomicInteger保证sum的原子性修改,避免多线程数据覆盖 private static final AtomicInteger sum = new AtomicInteger(0); private final int MOD = 3; private final int compare; private static final int LIMIT = 1000; // 把ArrayList包装成线程安全的容器,解决并发修改问题 private static final List<Integer> list = Collections.synchronizedList(new ArrayList<>()); public Tst(int compare) { this.compare = compare; } public void populate() throws InterruptedException { // 用类锁保证所有线程操作list时互斥,避免遍历和修改同时发生 synchronized (Tst.class) { for (int i = 0; i <= LIMIT; i++) { if (i % MOD == compare) { list.add(i); } } } } public void sum() throws InterruptedException { // 遍历线程安全容器时也加类锁,彻底避免并发修改异常 synchronized (Tst.class) { for (Integer num : list) { if (num % MOD == compare) { sum.addAndGet(num); } } } } @Override public void run() { try { populate(); sum(); System.out.println("线程" + compare + "执行后当前总和:" + sum.get()); } catch (InterruptedException ex) { Logger.getLogger(Tst.class.getName()).log(Level.SEVERE, null, ex); } } public static void main(String[] args) throws InterruptedException { Tst tst1 = new Tst(0); Tst tst2 = new Tst(1); Tst tst3 = new Tst(2); tst1.start(); tst2.start(); tst3.start(); // 等待所有线程执行完毕,再打印最终正确总和 tst1.join(); tst2.join(); tst3.join(); System.out.println("最终总和:" + sum.get()); } }
关键修改点说明
- 线程安全容器:用
Collections.synchronizedList把普通ArrayList包装成线程安全的容器,也可以用CopyOnWriteArrayList(适合读多写少的场景,遍历自带线程安全)。 - 统一类锁:用
synchronized (Tst.class)加类锁,保证三个线程操作list时完全互斥,彻底避免遍历和修改同时进行的情况。 - 原子性求和:把
int sum换成AtomicInteger,用addAndGet()方法保证修改操作的原子性,解决多线程同时修改导致的数据丢失问题。 - 等待线程完成:在main方法里调用
join(),等三个线程都执行完再打印最终总和,确保看到的是完整计算后的结果。
内容的提问来源于stack exchange,提问作者Sky Sumisu
相关产品推荐
相关产品推荐

