我调用了一个从多个线程将项目添加到列表中的方法,该方法未同步
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
[空,第 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]
在第一个和第二个输出中,分别有空项目,第一个输出中的第一个和第二个输出中的第二个项目。 而且列表的大小也不相同。
根据我的理解,这些项目不会在多线程中按顺序放置。我不知道在添加数组时这些项目有时会为空或跳过
我想了解此行为,为什么将空项目添加到数组中以及为什么在我的方法未同步时跳过项目
您宝贵的回答将对我有很大帮助, 提前致谢
List.add(Object)
函数不是原子函数。也就是说,如果多个线程尝试将元素添加到同一列表中,它们可能会相互干扰并破坏 add 函数的内部工作(调整内部数据结构的大小、计算列表中的项目位置......这可能会导致意外的输出甚至严重的问题(例如,在没有正确同步的情况下跨线程使用HashMap
- 即使是只读操作)。
如果使用同步List
:
static List<String> list = Collections.synchronizedList(new ArrayList<String>());
这将在list
实例上进行方法调用,synchronized
添加有效的原子操作。通过此更改,项目仍然不会按顺序添加,但至少可以确保正确添加所有项目。
因为添加项目不是原子操作,所以它需要多个操作,例如递增大小并将值分配给数组位置。
在多线程争用条件下,有时两个线程可能会同时递增,因此它只递增一次,而不是两次,因此最终大小小于 10。
有时,两个线程在分配值之前都会按顺序递增,因此跳过一个位置,两个线程都分配给相同的位置,一个线程覆盖另一个线程的值,因此一个 null 值和一个缺失值。
简而言之,多线程使用对象(如ArrayList
)会损坏列表。永远不要这样做。为什么你看到你所做的结果并不重要,只是不要这样做。