多线程操作ArrayList时B线程无法退出的问题及原因咨询
关于ArrayList多线程操作的两个问题解答
一、线程A执行list.add()、线程B执行list.size()的工作逻辑
咱们先拆解这两个方法的底层行为,再聊多线程下的核心问题:
list.add()的执行流程:ArrayList底层基于数组实现,执行add()时会先检查当前数组容量是否足够容纳新元素,不足的话就触发扩容(默认是原容量的1.5倍),接着把新元素放到数组对应位置,最后将内部的size变量加1。但整个过程没有任何同步锁,而且size++本身不是原子操作——拆分成了「读当前值→加1→写回新值」三个独立步骤。list.size()的执行流程:这个方法逻辑很简单,就是直接返回ArrayList内部的size变量值。
在多线程场景下会暴露出两个关键问题:
- 原子性问题:如果多个线程同时执行
add(),可能出现元素覆盖或者size计数错误的情况; - 可见性问题:线程A修改的
size值,线程B可能无法立刻读取到——因为每个线程都有自己的工作内存,会缓存size的副本,没有同步机制的话,线程B会一直读取旧的缓存值。
二、线程B永不退出的原因,以及添加代码后恢复正常的逻辑
先戳破你代码里的关键误区:你给list这个引用加了volatile,但**volatile只保证引用本身的可见性,不保证ArrayList内部size变量的可见性**!也就是说,只有当你把list重新赋值为新的ArrayList对象时,线程B才能立刻感知到变化,但线程A添加元素时修改的是ArrayList内部的size,这个变量并没有被volatile修饰,线程B的工作内存会一直缓存size的旧值(比如初始的0),所以它永远认为size没超过5,自然不会退出。
那为什么加一行代码就正常了?这行代码大概率触发了内存屏障,强制线程B刷新工作内存,读取到size的最新值,常见的几种情况:
- 换成
CopyOnWriteArrayList:这个类是线程安全的,内部的size变量用volatile修饰,而且修改操作是复制新数组,读取操作直接读旧数组,天然保证了可见性; - 添加同步代码块(比如
synchronized (list) {}):同步块的进入和退出会触发内存屏障,让线程B读取到主内存里的最新size值; - 添加
Thread.sleep()或者System.out.println():println方法内部有同步锁,sleep也会让线程暂时放弃CPU,间接触发内存刷新,让线程B读到最新的size。
比如把代码里的ArrayList换成CopyOnWriteArrayList,线程B就能立刻感知到size的变化,达到你预期的退出效果。
内容的提问来源于stack exchange,提问作者henry 4real
相关产品推荐
相关产品推荐

