AtomicReference存储异步填充集合时,能否获取全量元素?
问题解答:异步填充集合后能否通过AtomicReference拿到全部元素?
首先直接给结论:你大概率能看到打印出a,b,c,但这种写法存在严重的线程安全问题,绝对不能在生产代码里依赖这种行为。
为什么当前代码不可靠?
我们拆解下代码里的核心问题:
AtomicReference只保证集合引用本身的原子性,它管不了引用指向的ArrayList内部的修改。也就是说,它确保你不会拿到一个被中途替换的List对象,但完全不保证你能看到其他线程对这个List内部元素的修改。ArrayList本身是非线程安全的集合:当一个线程在执行add操作(修改集合),另一个线程在读取集合内容时,没有任何同步机制来保证内存可见性和操作原子性。- 从Java内存模型(JMM)的角度看,异步线程对ArrayList的修改可能被缓存到线程本地内存,主线程即使等了10秒,也可能看不到这些修改(极端但符合规范的情况)。
- 更糟的是,ArrayList的
add操作在扩容时会修改内部数组和size变量,并发下可能导致集合结构损坏,甚至抛出ConcurrentModificationException或者出现元素丢失的情况。
你现在能看到正确结果,只是因为主线程休眠时间足够长,且JVM没有触发极端的内存优化,但这完全是巧合,不是可靠的逻辑。
正确的实现方式
要安全地实现这个需求,有几种常见方案:
1. 使用线程安全的集合
把ArrayList换成线程安全的集合,比如CopyOnWriteArrayList(适合读多写少的场景),或者Collections.synchronizedList:
private List<String> someFunc() { List<String> list = new CopyOnWriteArrayList<>(); new Thread(() -> { try { list.add("a"); Thread.sleep(1000); list.add("b"); Thread.sleep(1000); list.add("c"); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } }).start(); return list; }
线程安全集合会内部处理同步和内存可见性问题,确保主线程能看到异步线程的修改。
2. 使用同步机制等待任务完成
比如用CountDownLatch明确等待异步线程完成所有元素添加:
private List<String> someFunc() throws InterruptedException { List<String> list = new ArrayList<>(); CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { try { list.add("a"); Thread.sleep(1000); list.add("b"); Thread.sleep(1000); list.add("c"); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } finally { latch.countDown(); // 任务完成,通知主线程 } }).start(); latch.await(); // 等待异步线程完成 return list; }
这种方式不仅保证主线程只在任务完成后获取集合,还通过CountDownLatch的同步操作保证了内存可见性。
3. 使用Future规范处理异步任务
用ExecutorService提交异步任务,通过Future获取结果,这是Java处理异步任务的标准方式:
private Future<List<String>> someFunc() { ExecutorService executor = Executors.newSingleThreadExecutor(); return executor.submit(() -> { List<String> list = new ArrayList<>(); list.add("a"); Thread.sleep(1000); list.add("b"); Thread.sleep(1000); list.add("c"); Thread.sleep(1000); executor.shutdown(); return list; }); } // 主线程调用 Future<List<String>> futureList = someFunc(); // 或者直接用futureList.get()等待结果,无需额外sleep Thread.sleep(10000); System.out.print(String.join(",", futureList.get()));
总结
你的代码在测试环境可能多次得到正确结果,但这是不可靠的。必须通过线程安全集合、同步机制或者标准异步任务框架来保证逻辑的正确性。
内容的提问来源于stack exchange,提问作者Rafay
相关产品推荐
相关产品推荐

