Java中volatile变量可见性保障及实践有效性确认问询
关于volatile listSize保障ArrayList读写可见性的可靠性分析
首先直接给出结论:你这个用法在可见性层面是可靠的,不会出现reader线程无限阻塞的情况,但存在ArrayList本身非线程安全带来的潜在风险。
为什么volatile listSize能解决可见性问题?
你提到的《Java Concurrency in Practice》里的happen-before规则是核心依据:
- 当线程A写入一个volatile变量时,所有在写入之前A对共享变量的修改,都会对后续读取这个volatile变量的线程B可见。写入volatile相当于退出同步块,读取相当于进入同步块的类比完全准确。
- 在你的代码中,writer线程先执行
list.add(1)(修改ArrayList的内部元素与结构),再执行listSize++(写入volatile变量)。根据happen-before规则,reader线程读取到最新的listSize值时,必然能看到writer之前对list做的所有修改。 - 无volatile修饰listSize时,reader线程的CPU可能缓存listSize的旧值,导致
currentIndex>=listSize的判断永远为true,陷入无限循环;加了volatile后,reader每次读取listSize都会强制从主内存获取最新值,因此能及时感知到writer的元素添加操作。
这个方案的潜在风险
虽然可见性问题解决了,但ArrayList本身不是线程安全的集合类,你的代码存在以下隐患:
- ArrayList的
add方法在扩容时(元素数量超过当前数组容量)会执行数组复制操作,该过程无同步机制。如果reader线程在writer扩容过程中调用list.get(currentIndex),可能读到未初始化的数组元素,甚至抛出ArrayIndexOutOfBoundsException。 - 即使不触发扩容,
add方法内部的size++(ArrayList自身的size变量)也不是原子操作——不过你用自定义的listSize跟踪数量,暂时规避了这个问题,但ArrayList内部的其他状态(比如elementData数组的引用)仍可能出现不一致。
改进建议
如果要让这段代码完全线程安全,推荐两种方案:
- 用CopyOnWriteArrayList替代ArrayList:这是Java并发包专为读多写少场景设计的集合,每次写入操作都会复制一份新数组,读取操作完全无锁,天然保证可见性与线程安全,无需额外的volatile变量。
- 给list的读写操作加锁:在writer的
list.add(1)和reader的list.get(currentIndex)外层加上synchronized块(锁对象可选用list本身或专门的锁),同时将listSize的操作放在锁内,既保证可见性,也避免ArrayList的线程安全问题。
附你的测试代码:
public class Main { static ArrayList<Integer> list = new ArrayList<>(); static volatile int listSize = 0; public static void main(String[] args) { Thread writer = new Thread(Main::write); Thread reader = new Thread(Main::read); writer.start(); reader.start(); } public static void write() { while (true) { try { Thread.sleep(10000); } catch (InterruptedException e) { return; } list.add(1); listSize++; System.out.println("Added an element."); } } public static void read() { int currentIndex = 0; while (true) { if(currentIndex>=listSize) { continue; } System.out.println("Read an element."); int element = list.get(currentIndex); currentIndex++; // do stuff with element... } } }
内容的提问来源于stack exchange,提问作者iexav
相关产品推荐
相关产品推荐

