我调用了一个方法,该方法将项添加到多个线程的列表中,该方法未同步
static List<String> list = new ArrayList<String>();
static void addItem(int itemNo){
list.add("item "+itemNo);
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++)
{
int itemNo = i;
Runnable task = () -> addItem(itemNo);
executor.execute(task);
}
executor.shutdown();
System.out.println(list);
}
每次执行此代码时,打印列表的输出都是随机的
I-E
[null,第1项,第4项,第5项,第6项,第7项,第8项,第9项]
[第1项,无效,第2项,第4项,第5项,第3项,第6项,第7项,第8项,第9项]
[第1项,第2项,第3项,第4项,第5项,第6项,第7项,第8项,第9项]
在第1和第2个输出中,有空项目,第1个输出中的第1个和第2个中的第2个。并且列表的大小也不相同。
根据我的理解,这些项目不会在多线程中按顺序排列。在添加数组
时,我不知道有时这些项会为null或跳过我想了解这种行为,为什么将null项添加到数组中以及当我的方法不是同步
时跳过项的原因你的宝贵答案会对我有所帮助, 提前谢谢
答案 0 :(得分:3)
因为添加项目不是原子操作,所以它需要多个操作,例如递增大小并将值赋给数组位置。
对于多线程竞争条件,有时两个线程可能会同时增加,因此它只增加一次,而不是两次,因此最终大小小于10.
有时两个线程在分配值之前都会按顺序递增,因此跳过一个位置并且两个线程都分配到相同的位置,一个线程覆盖另一个线程的值,因此空值和缺失值。
简而言之,对象的多线程使用(如ArrayList
)将损坏列表。永远不要这样做。为什么你看到你做的结果并不重要,只是不要这样做。
答案 1 :(得分:3)
List.add(Object)
函数不是原子函数。也就是说,如果多个线程尝试将元素添加到同一个列表中,它们可能会相互干扰并破坏add函数的内部工作(调整内部数据结构的大小,计算列表中的项目位置,... )。这可能会导致意外输出甚至是严重问题(例如,在没有正确同步的情况下使用HashMap
交叉线程时 - 即使对于只读操作也是如此。)
如果您使用同步的List
:
static List<String> list = Collections.synchronizedList(new ArrayList<String>());
这将在list
实例synchronized
上进行方法调用,从而增加了一个有效的原子操作。通过此更改,仍然不会按顺序添加项目,但至少确保所有项目都将正确添加。