多个线程执行相同的方法(非同步)行为

时间:2017-05-23 07:17:26

标签: java multithreading arraylist synchronization executorservice

我调用了一个方法,该方法将项添加到多个线程的列表中,该方法未同步

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项添加到数组中以及当我的方法不是同步

时跳过项的原因

你的宝贵答案会对我有所帮助, 提前谢谢

2 个答案:

答案 0 :(得分:3)

因为添加项目不是原子操作,所以它需要多个操作,例如递增大小并将值赋给数组位置。

对于多线程竞争条件,有时两个线程可能会同时增加,因此它只增加一次,而不是两次,因此最终大小小于10.

有时两个线程在分配值之前都会按顺序递增,因此跳过一个位置并且两个线程都分配到相同的位置,一个线程覆盖另一个线程的值,因此空值和缺失值。

简而言之,对象的多线程使用(如ArrayList)将损坏列表。永远不要这样做。为什么你看到你做的结果并不重要,只是不要这样做。

答案 1 :(得分:3)

List.add(Object)函数不是原子函数。也就是说,如果多个线程尝试将元素添加到同一个列表中,它们可能会相互干扰并破坏add函数的内部工作(调整内部数据结构的大小,计算列表中的项目位置,... )。这可能会导致意外输出甚至是严重问题(例如,在没有正确同步的情况下使用HashMap交叉线程时 - 即使对于只读操作也是如此。)

如果您使用同步的List

 static List<String> list = Collections.synchronizedList(new ArrayList<String>());

这将在list实例synchronized上进行方法调用,从而增加了一个有效的原子操作。通过此更改,仍然不会按顺序添加项目,但至少确保所有项目都将正确添加。