有三个方面,如果您使用 ArrayList(例如)而没有充分同步,则可能会出错。
第一种情况是,如果两个线程碰巧同时更新 ArrayList,则它可能会损坏。例如,追加到列表的逻辑是这样的:
public void add(T element) {
if (!haveSpace(size + 1)) {
expand(size + 1);
}
elements[size] = element;
// HERE
size++;
}
现在假设我们有一个处理器/内核和两个线程“同时”在同一列表上执行此代码。假设第一个线程到达标记的点并被抢占。第二个线程出现,并覆盖插槽,因为第一个线程刚刚使用自己的元素更新,然后递增。当第一个线程最终获得控制权时,它会更新 。最终结果是,我们添加了第二个线程的元素,而不是第一个线程的元素,并且很可能还向列表中添加了 。(这只是说明性的。实际上,本机代码编译器可能已对代码进行了重新排序,依此类推。但关键是,如果同时进行更新,可能会发生不好的事情。HERE
elements
size
size
null
第二种情况是由于 CPU 的缓存内存中主内存内容的缓存而产生的。假设我们有两个线程,一个向列表添加元素,另一个线程读取列表的大小。当 on thread 添加元素时,它将更新列表的属性。但是,由于 不是 ,因此 的新值可能不会立即写出到主内存中。相反,它可以位于高速缓存中,直到 Java 内存模型要求刷新高速缓存的写入的同步点。同时,第二个线程可以调用列表并获取过时值 。在最坏的情况下,第二个线程(例如调用)可能会看到 不一致的 和 数组值,从而导致意外异常。(请注意,即使只有一个内核并且没有内存缓存,也可能发生此类问题。JIT 编译器可以自由地使用 CPU 寄存器来缓存内存内容,并且当发生线程上下文切换时,这些寄存器不会相对于其内存位置进行刷新/刷新。size
size
volatile
size
size()
size
get(int)
size
elements
第三种情况是当您同步 ;例如,将其包装为 .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);
}
如果 thread2 的列表是 or,并且两个线程同时运行,则线程 2 将失败,并显示 .如果是其他(家庭酿造)列表,则结果是不可预测的。问题在于,制作同步列表不足以使其相对于不同线程执行的一系列列表操作的线程安全。为了获得这一点,应用程序通常需要在更高级别/更粗粒度上进行同步。ArrayList
LinkedList
ConcurrentModificationException
list
另外,我记得我被告知多个线程并没有真正同时运行,1个线程运行了一段时间,另一个线程在那之后运行(在具有单个CPU的计算机上)。
正确。如果只有一个内核可用于运行应用程序,则显然一次只能运行一个线程。这使得一些危险变得不可能,而其他危险发生的可能性要小得多。但是,操作系统可以在代码中的任何位置和任何时间从一个线程切换到另一个线程。
如果这是正确的,两个线程怎么可能同时访问相同的数据?也许线程 1 会在修改过程中停止,线程 2 将启动?
是的。这是可能的。它发生的概率非常小1,但这只会使这种问题更加阴险。
1 - 这是因为在硬件时钟周期的时间尺度上测量时,线程时间切片事件非常少见。