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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:03:27