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

时间:2010-04-26 18:46:03

标签: java concurrency synchronization arraylist

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

我的理论是,当add被两个线程同时调用时,添加的两个对象中只有一个实际上被添加到ArrayList。这有可能吗?

如果是这样,你怎么解决这个问题?使用像Vector

这样的同步集合

9 个答案:

答案 0 :(得分:48)

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

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

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

答案 1 :(得分:15)

任何事情都可能发生。您可以正确添加两个对象。您只能添加其中一个对象。您可能会获得ArrayIndexOutOfBounds异常,因为未正确调整基础数组的大小。或者其他事情可能发生。我只想说你不能依赖任何行为。

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

答案 2 :(得分:7)

您还可以获得nullArrayOutOfBoundsException或其他与实施相关的内容。已经观察到HashMap在生产系统中进入无限循环。你真的不需要知道可能出现什么问题,只是不要这样做。

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

答案 3 :(得分:5)

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

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,程序会输出正确的结果。

答案 4 :(得分:3)

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

答案 5 :(得分:3)

由于ArrayList不是线程安全的,因此行为可能未定义。如果在Iterator对其进行交互时修改列表,则会出现ConcurrentModificationException。您可以使用Collection.synchronizedList包装ArrayList或使用线程安全的集合(有很多),或者只是将add调用放在synchronized块中。

答案 6 :(得分:1)

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

答案 7 :(得分:0)

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

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

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

因此,事情变得不同步,结果令人不快和不可预测。

答案 8 :(得分:0)

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

Collections.synchronizedList( new ArrayList() );

new Vector();

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

  • 50-100%更快
  • 可以使用现有的ArrayList