线程间同步ArrayList未返回预期值,求排查代码问题
问题分析与解决方案
咱们先来拆解你遇到的问题,其实核心不是ArrayList的线程安全问题(你已经用synchronized保护了add操作),而是对线程结束时打印的size的理解偏差,以及一个容易忽略的同步细节:
1. 线程结束时的打印结果并非你预期的"单线程贡献值"
你的两个线程共享同一个taskList,当其中一个线程执行完1000000次add准备打印时,另一个线程大概率还在执行add操作。举个例子:
- 线程A刚完成第1000000次
add,此时列表大小是1000000 - 但线程B已经执行了500000次
add,所以线程A打印的size会是1500000,而非你预期的1000000
这不是元素被覆盖了,而是两个线程的操作在并发进行,打印动作没有和add操作同步,导致你看到的是当前时刻的总大小,而非单个线程完成添加后的状态。实际上你的代码不会丢失元素,最终列表的总大小一定是2000000。
2. 为什么替换Vector、加volatile等操作没用?
Vector的add是线程安全的,但同样解决不了打印size时的并发问题——size()方法本身是原子的,但从你完成最后一次add到调用size()的间隙,其他线程可能已经修改了列表。volatile修饰列表引用也没用:volatile只能保证引用的可见性,不能保证列表内部状态(比如size)的原子性读取和修改的一致性,而且你已经用synchronized保护了add,可见性已经有保障了。- 给构造方法加同步完全没必要,因为构造方法是在主线程调用的,不存在并发问题。
如何修正代码达到你的预期?
如果你想让每个线程打印自己完成添加后的列表大小,可以用以下两种方案:
方案1:同步打印动作
把打印也放到同步块中,确保读取size时没有其他线程在修改列表:
@Override public void run() { for( int i = 0; i < 1000000;i++){ updateList(i); } // 用同一个锁保护size的读取 synchronized (myList) { System.out.println("end: " + myList.size()); } }
这样第一个线程完成所有添加后,会先获取锁打印此时的size(1000000),第二个线程要等第一个线程释放锁后才能继续添加,等它打印时size就是2000000。
方案2:线程独立计数(更高效)
每个线程自己统计添加的数量,不需要同步打印,能更清晰看到单个线程的贡献:
class MyThread implements Runnable{ private List<Integer> myList; private int ownCount = 0; // 线程独立计数 public MyThread(List<Integer> list){ this.myList = list; } private void updateList(int i){ synchronized (myList) { myList.add(i); } ownCount++; } @Override public void run() { for( int i = 0; i < 1000000;i++){ updateList(i); } System.out.println("我添加了" + ownCount + "个元素,当前总大小: " + myList.size()); } }
另外补充一个小优化:你创建了10000个线程的线程池,但只提交了2个任务,这完全没必要,线程池大小设为2就足够了,避免不必要的资源消耗。
内容的提问来源于stack exchange,提问作者Vahid Hashemi
相关产品推荐
相关产品推荐

