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

Multiple Threads executing same Method (Non Synchronized) behavior

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

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);
    
}

打印列表的输出是随机的,每次我执行这段代码

我-e

[null, item 1, item 4, item 5, item 6, item 7, item 8, item 9]

[item 1, null, item 2, item 4, item 5, item 3, item 6, item 7, item 8, item 9]

[item 1, item 2, item 3, item 4, item 5, item 6, item 7, item 8, item 9]

在第一个和第二个输出中,有空项,第一个输出中有第一个,第二个输出中有第二个。而且列表的大小也不相同。

据我了解,在多线程中,项目不会按顺序排列。我不知道在添加数组

时这些项目有时会为空或被跳过

我想了解此行为,为什么将空项添加到数组以及为什么在我的方法未 Synchronized

时跳过项

您宝贵的回答对我有很大帮助, 提前致谢

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

在多线程竞争条件下,有时两个线程可能同时递增,所以它只递增一次,而不是两次,因此最终大小小于 10。

有时两个线程在赋值之前都会按顺序递增,所以一个位置被跳过,两个线程都赋值给同一个位置,一个线程覆盖另一个线程的值,因此一个空值和一个缺失值。

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

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

如果你使用同步 List:

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

这将对 list 实例 synchronized 进行方法调用,使添加成为一个有效的原子操作。通过此更改,项目仍然不会按顺序添加,但至少可以确保所有项目都将正确添加。