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

ArrayList线程不安全实例异常咨询:先add后remove为何仍报错?

为什么你的非线程安全ArrayList会抛出IndexOutOfBoundsException?

嘿,这个问题正好戳中了非线程安全集合的一个隐性坑——你从宏观逻辑上觉得“每个remove都有add前置”,但多线程下集合内部的状态(比如size变量)存在竞态条件,导致线程看到的状态和集合真实状态不一致,最终触发了看起来“矛盾”的异常。

核心原因:ArrayList的状态没有线程同步保护

ArrayList的size是一个普通int变量,既没有volatile修饰(保证线程间状态可见性),也没有同步锁保护读写操作。同时,add和remove都是多步骤的非原子操作,这些步骤在多线程下很容易被交错执行,导致状态混乱。

触发你看到的异常的典型场景

你遇到的异常是java.lang.IndexOutOfBoundsException: Index: 0, Size: 1——明明0<1,却抛出索引越界,这其实是因为异常抛出的瞬间,size的值被其他线程修改了,具体流程如下:

  1. 初始状态:ArrayList的size为0,数组为空。
  2. 线程A执行method2()(add操作):
    • 完成元素存储(把"1"放到数组的index0位置),但还没执行size++(add方法里这两步是分开的),此时size还是0。
  3. 线程B执行method3()(remove(0)):
    • 进入rangeCheck(0)判断:读取到size为0,满足0 >= 0的异常条件,准备抛出异常。
    • 就在调用异常信息生成方法outOfBoundsMsg的瞬间,线程A刚好执行完size++,把size改成了1。
  4. 最终异常信息:outOfBoundsMsg会读取当前最新的size值,于是就出现了“Index: 0, Size: 1”的矛盾提示。

除此之外,还有其他可能的竞态场景:比如两个线程同时执行remove,一个线程已经把size改成0,另一个线程还停留在之前size=1的判断逻辑里,后续执行数组操作时触发越界。

总结

你宏观上的“add次数等于remove次数”是最终的结果,但在多线程的交错执行中,线程对集合状态的读取和修改完全不同步:

  • 某个线程执行remove时,可能另一个线程的add操作还没完成size的更新,导致remove看到的是旧的size值。
  • 甚至在抛出异常的瞬间,size被其他线程修改,让异常信息看起来“不合逻辑”。

解决这个问题的方法很直接:

  • 使用线程安全的集合,比如CopyOnWriteArrayList,或者用Collections.synchronizedList(new ArrayList<>())包装ArrayList。
  • 在method2和method3方法上加上synchronized关键字,用锁保护所有对ArrayList的操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 14:47:41