Java synchronized同步ArrayList时size与stream.count()结果不一致问题
问题分析与解答
核心原因:主线程未等待子线程执行完毕
你启动101个线程执行add操作后,主线程没有做任何等待,直接就去读取list.size()。这时候部分子线程可能还没来得及执行完synchronized块里的add操作,所以size()返回的是当前时刻已经完成添加的元素数量,自然会出现小于101的情况。
为什么累加结果是5050?
这是执行时机带来的巧合:主线程执行完size()后,到执行stream().reduce()这段时间里,那些还没完成add的子线程刚好跑完了所有添加操作。等stream遍历的时候,List里已经有了全部101个元素,所以累加和刚好是0到100的总和5050。
为什么改用list.stream().count()结果正确?
同样是时机问题:当你把size()换成stream().count()时,主线程执行这个方法的时刻,所有子线程已经完成了add操作(或者刚好在执行count前全部完成)。本质上还是主线程没有等待子线程的问题,stream().count()本身并没有额外的同步机制,只是它执行的时机刚好赶上了所有元素添加完成。
正确的解决方式
要确保主线程等待所有子线程执行完毕,可以用Thread.join()或者CountDownLatch:
方式1:用Thread.join()
import java.util.ArrayList; import java.util.List; public class safeList { public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<>(); List<Thread> threads = new ArrayList<>(); for (int i = 0; i < 101; i++) { int finalI = i; Thread t = new Thread(() -> { synchronized (list) { list.add(Integer.toString(finalI)); } }); threads.add(t); t.start(); } // 等待所有线程执行完毕 for (Thread t : threads) { t.join(); } System.out.println(list.size()); // 必为101 String string = list.stream() .map(Integer::valueOf) .reduce(0, Integer::sum) .toString(); System.out.println(string); // 必为5050 } }
方式2:用CountDownLatch
import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; public class safeList { public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<>(); CountDownLatch latch = new CountDownLatch(101); for (int i = 0; i < 101; i++) { int finalI = i; new Thread(() -> { try { synchronized (list) { list.add(Integer.toString(finalI)); } } finally { latch.countDown(); } }).start(); } // 等待所有线程执行完毕 latch.await(); System.out.println(list.size()); // 必为101 String string = list.stream() .map(Integer::valueOf) .reduce(0, Integer::sum) .toString(); System.out.println(string); // 必为5050 } }
另外,虽然你用synchronized保证了add操作的原子性,但ArrayList本身的size()方法没有同步修饰,所以主线程读取size时,没有内存可见性保证(可能读到的是缓存中的旧值)。不过在这个场景里,核心问题还是线程执行顺序的时机,而非内存可见性。
内容的提问来源于stack exchange,提问作者1earing
相关产品推荐
相关产品推荐

