Java 中的 ArrayList 和多线程 [英] ArrayList and Multithreading in Java

查看:27
本文介绍了Java 中的 ArrayList 和多线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Under what circumstances would an unsynchronized collection, say an ArrayList, cause a problem? I can't think of any, can someone please give me an example where an ArrayList causes a problem and a Vector solves it? I wrote a program that have 2 threads both modifying an arraylist that has one element. One thread puts "bbb" into the arraylist while the other puts "aaa" into the arraylist. I don't really see an instance where the string is half modified, I am on the right track here?

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

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?

非常感谢.

推荐答案

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

There are three aspects of what might go wrong if you use an ArrayList (for example) without adequate synchronization.

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

The first scenario is that if two threads happen to update the ArrayList at the same time, then it may get corrupted. For instance, the logic of appending to a list goes something like this:

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

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

Now suppose that we have one processor / core and two threads executing this code on the same list at the "same time". Suppose that the first thread gets to the point labeled HERE and is preempted. The second thread comes along, and overwrites the slot in elements that the first thread just updated with its own element, and then increments size. When the first thread finally gets control, it updates size. The end result is that we've added the second thread's element and not the first thread's element, and most likely also added a null to the list. (This is just illustrative. In reality, the native code compiler may have reordered the code, and so on. But the point is that bad things can happen if updates happen simultaneously.)

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

The second scenario arises due to the caching of main memory contents in the CPU's cache memory. Suppose that we have two threads, one adding elements to the list and the second one reading the list's size. When on thread adds an element, it will update the list's size attribute. However, since size is not volatile, the new value of size may not immediately be written out to main memory. Instead, it could sit in the cache until a synchronization point where the Java memory model requires that cached writes get flushed. In the meantime, the second thread could call size() on the list and get a stale value of size. In the worst case, the second thread (calling get(int) for example) might see inconsistent values of size and the elements array, resulting in unexpected exceptions. (Note that kind of problem can happen even when there is only one core and no memory caching. The JIT compiler is free to use CPU registers to cache memory contents, and those registers don't get flushed / refreshed with respect to their memory locations when a thread context switch occurs.)

当您对ArrayList 进行同步操作时,会出现第三种情况;例如通过将其包装为 SynchronizedList.

The third scenario arises when you synchronize operations on the ArrayList; e.g. by wrapping it as a 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 成为同步列表并不足以使其对于由不同线程执行的序列列表操作而言是线程安全的.为此,应用程序通常需要在更高级别/更粗粒度上进行同步.

If thread2's list is an ArrayList or LinkedList and the two threads run simultaneously, thread 2 will fail with a ConcurrentModificationException. If it is some other (home brew) list, then the results are unpredictable. The problem is that making list a synchronized list is NOT SUFFICIENT to make it thread-safe with respect to a sequence of list operations performed by different threads. To get that, the application would typically need to synchronize at a higher level / coarser grain.

另外,我记得有人告诉我多个线程并不是真正同时运行,一个线程运行了一段时间,然后另一个线程运行(在具有单个 CPU 的计算机上).

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).

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

Correct. If there is only one core available to run the application, obviously only one thread gets to run at a time. This makes some of the hazards impossible and others become much less likely likely to occur. However, it is possible for the OS to switch from one thread to another thread at any point in the code, and at any time.

如果这是正确的,两个线程怎么可能同时访问相同的数据?也许线程 1 会在修改某些东西的过程中停止而线程 2 会启动?

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,但这只会让这种问题更加阴险.

Yup. That's possible. The probability of it happening is very small1 but that just makes this kind of problem more insidious.

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

这篇关于Java 中的 ArrayList 和多线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆