繁体   English   中英

同时添加到 ArrayList 的并发线程 - 会发生什么?

[英]Concurrent threads adding to ArrayList at same time - what happens?

我们有多个线程在ArrayList上调用add(obj)

我的理论是,当两个线程同时调用add时,只有被添加的两个对象中的一个真正添加到ArrayList中。 这合理吗?

如果是这样,你如何解决这个问题? 使用像Vector这样的同步集合?

当 ArrayList 上的两个线程同时调用 add 时会发生什么,并不能保证行为。 但是,根据我的经验,这两个对象都被很好地添加了。 大多数与列表相关的线程安全问题都在添加/删除时处理迭代。 尽管如此,我强烈建议不要使用具有多线程和并发访问的 vanilla ArrayList。

Vector 曾经是并发列表的标准,但现在的标准是使用Collections 同步列表

此外,如果您打算花时间在 Java 中使用线程,我强烈推荐 Goetz 等人的 Java Concurrency in Practice。 这本书更详细地讨论了这个问题。

任何数量的事情都可能发生。 您可以正确添加两个对象。 您只能添加其中一个对象。 您可能会收到 ArrayIndexOutOfBounds 异常,因为未正确调整底层数组的大小。 或者可能会发生其他事情。 只要说你不能依赖任何发生的行为就够了。

作为替代方案,您可以使用Vector ,您可以使用Collections.synchronizedList ,您可以使用CopyOnWriteArrayList ,或者您可以使用单独的锁。 这一切都取决于您在做什么以及您对集合的访问权限的控制类型。

您还可以获得一个null 、一个ArrayOutOfBoundsException或由实现保留的东西。 已观察到HashMap在生产系统中进入无限循环。 你真的不需要知道什么可能会出错,只是不要这样做。

您可以使用Vector ,但它往往会导致界面不够丰富。 您可能会发现在大多数情况下您需要不同的数据结构。

我想出了以下代码来模拟真实世界的场景。

100 个任务并行运行,它们将完成状态更新到主程序。 我使用 CountDownLatch 等待任务完成。

import java.util.concurrent.*;
import java.util.*;

public class Runner {

    // Should be replaced with Collections.synchronizedList(new ArrayList<Integer>())
    public List<Integer> completed = new ArrayList<Integer>();

    /**
     * @param args
     */
    public static void main(String[] args) {
        Runner r = new Runner();
        ExecutorService exe = Executors.newFixedThreadPool(30);
        int tasks = 100;
        CountDownLatch latch = new CountDownLatch(tasks);
        for (int i = 0; i < tasks; i++) {
            exe.submit(r.new Task(i, latch));
        }
        try {
            latch.await();
            System.out.println("Summary:");
            System.out.println("Number of tasks completed: "
                    + r.completed.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        exe.shutdown();
    }

    class Task implements Runnable {

        private int id;
        private CountDownLatch latch;

        public Task(int id, CountDownLatch latch) {
            this.id = id;
            this.latch = latch;
        }

        public void run() {
            Random r = new Random();
            try {
                Thread.sleep(r.nextInt(5000)); //Actual work of the task
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            completed.add(id);
            latch.countDown();
        }
    }
}

当我运行应用程序 10 次并且至少运行 3 到 4 次时,程序没有打印正确数量的已完成任务。 理想情况下,它应该打印 100(如果没有异常发生)。 但在某些情况下,它会打印 98、99 等。

因此它证明了 ArrayList 的并发更新不会给出正确的结果。

如果我用同步版本替换 ArrayList,程序会输出正确的结果。

你可以使用List l = Collections.synchronizedList(new ArrayList()); 如果你想要arrayList的线程安全版本。

该行为可能是未定义的,因为 ArrayList 不是线程安全的。 如果您在迭代器对其进行交互时修改列表,那么您将获得 ConcurrentModificationException。 您可以使用 Collection.synchronizedList 包装 ArrayList 或使用线程安全的集合(有很多),或者只是将添加调用放在同步块中。

您可以使用而不是ArrayList();

Collections.synchronizedList( new ArrayList() );

或者

new Vector();

synchronizedList对我来说更可取,因为它是:

  • 速度快 50-100%
  • 可以与现有的 ArrayList 一起使用

在我最近使用 ArrayList 从不同线程添加新元素的经验中,会错过其中的一些,因此使用 Collections.synchronizedList(new ArrayList()) 可以避免这个问题。

List<String> anotherCollection = new ArrayList<>();
List<String> list = new ArrayList<>();
// if 'anotherCollection' is bigger enough it will miss some elements.
anotherCollection.parallelStream().forEach(el -> list.add("element" + el));

List<String> listSync = Collections.synchronizedList(new ArrayList<>());
// regardless of 'anotherCollection' is bigger it will add all the elements.
anotherCollection.parallelStream().forEach(el -> list.add("element" + el));

java.util.concurrent 有一个线程安全的数组列表。 标准的 ArrayList 不是线程安全的,多个线程同时更新时的行为是未定义的。 当一个或多个线程同时写入时,多个读取器也会出现奇怪的行为。

http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html

请注意,此实现不同步。 如果多个线程同时访问一个 ArrayList 实例,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。

由于内部没有同步,因此您的理论是不合理的。

因此,事情变得不同步,结果令人不快且无法预测。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM