java - Java中的ArrayList和多线程

标签 java multithreading arraylist

在什么情况下不同步的集合,比如 ArrayList,会导致问题?我想不出任何问题,有人可以给我一个例子,其中 ArrayList 导致问题,而 Vector 解决问题?我编写了一个有 2 个线程的程序,它们都修改了一个具有一个元素的数组列表。一个线程将“bbb”放入arraylist,而另一个线程将“aaa”放入arraylist。我真的没有看到字符串被修改了一半的实例,我在这里是在正确的轨道上吗?

另外,我记得有人告诉我,多个线程并没有真正同时运行,一个线程运行了一段时间,然后另一个线程运行(在具有单个 CPU 的计算机上)。如果这是正确的,那么两个线程怎么可能同时访问相同的数据呢?也许线程 1 会在修改某些东西的过程中停止,而线程 2 会启动?

非常感谢。

最佳答案

如果您在没有充分同步的情况下使用 ArrayList(例如),可能会出现三个方面的问题。

第一种情况是如果两个线程碰巧同时更新 ArrayList,那么它可能会损坏。例如,追加到列表的逻辑是这样的:

public void add(T element) {
    if (!haveSpace(size + 1)) {
        expand(size + 1);
    }
    elements[size] = element;
    // HERE
    size++;
}

现在假设我们有一个处理器/内核和两个线程“同时”在同一个列表上执行此代码。假设第一个线程到达标记为 HERE 的点并被抢占。第二个线程出现,并覆盖 elements 中第一个线程刚刚用自己的元素更新的槽,然后增加 size。当第一个线程最终获得控制权时,它会更新 size。最终结果是我们添加了第二个线程的元素而不是第一个线程的元素,并且很可能还添加了一个 null 到列表中。 (这只是说明性的。实际上, native 代码编译器可能已经对代码进行了重新排序,等等。但关键是如果同时发生更新,可能会发生坏事。)

第二种情况是由于在 CPU 的高速缓存中缓存主内存内容而出现的。假设我们有两个线程,一个向列表添加元素,第二个读取列表的大小。当线程添加一个元素时,它将更新列表的 size 属性。但是,由于 size 不是 volatile,因此 size 的新值可能不会立即写入主存储器。相反,它可以一直位于缓存中,直到 Java 内存模型要求缓存的写入被刷新的同步点。同时,第二个线程可以调用列表中的 size() 并获得一个旧的 size 值。在最坏的情况下,第二个线程(例如调用 get(int))可能会看到 sizeelements 数组的值不一致,从而导致在意外的异常中。 (请注意,即使只有一个内核且没有内存缓存,也会发生这种问题。JIT 编译器可以自由使用 CPU 寄存器来缓存内存内容,并且这些寄存器不会被刷新/刷新相对于它们的内存位置当发生线程上下文切换时。)

第三种情况出现在同步ArrayList上的操作时;例如通过将其包装为 SynchronizedList.

    List list = Collections.synchronizedList(new ArrayList());

    // Thread 1
    List list2 = ...
    for (Object element : list2) {
        list.add(element);
    }

    // Thread 2
    List list3 = ...
    for (Object element : list) {
        list3.add(element);
    }

如果线程 2 的列表是 ArrayListLinkedList 并且两个线程同时运行,则线程 2 将失败并返回 ConcurrentModificationException。如果是其他(自制)列表,则结果是不可预测的。问题在于,使 list 成为同步列表不足以使其相对于由不同线程执行的列表操作的 序列 而言是线程安全的。为此,应用程序通常需要在更高级别/更粗粒度上进行同步。


Also, I remember that I was told that multiple threads are not really running simultaneously, 1 thread is run for sometime and another thread runs after that(on computers with a single CPU).

正确。如果只有一个内核可用于运行应用程序,显然一次只能运行一个线程。这使得一些危险成为不可能,而另一些则不太可能发生。但是,操作系统可以在代码中的任何时间点从一个线程切换到另一个线程。

If that was correct, how could two threads ever access the same data at the same time? Maybe thread 1 will be stopped in the middle of modifying something and thread 2 will be started?

是的。这是可能的。它发生的可能性非常小1,但这只会让这种问题更加隐蔽。


1 - 这是因为线程时间片事件在硬件时钟周期的时间尺度上测量时非常罕见。

关于java - Java中的ArrayList和多线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3589308/

相关文章:

java - 如何在 Java 中以编程方式更改 logback 日志级别?

java - 关闭旧的套接字后创建新的套接字导致连接被拒绝

检查 pthread_cond_t 的值

java - 初始化数组列表

java - 调试二十一点程序? ( java )

java - 数组列表中的随机单词

java - session.invalidate() 的奇怪问题

java - document.close() 需要很长时间才能将 pdf 数据写入硬盘

multithreading - 哪个线程安全的JobRepository用于多线程步骤?

c++ - 休眠/重启线程