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

Mar*_*eon 51 java concurrency synchronization arraylist

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

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

如果是这样,你怎么解决这个问题?使用同步集合Vector吗?

der*_*ion 52

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

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

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

  • "根据我的经验,这两个对象都被添加得很好"只是想指出这只是纯粹的运气.可能与ArrayList的数据损坏问题的窗口非常小,但它仍然存在 (16认同)
  • 是的,这就是我的观点.绝大多数情况下都没有问题; 但是我们不会对大多数情况进行编程.因此我强烈建议不要使用ArrayList. (6认同)

Mat*_*ler 16

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

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


Tom*_*ine 9

您还可以获得实现的一个null,一个ArrayOutOfBoundsException或一些东西.HashMap已经观察到s在生产系统中进入无限循环.你真的不需要知道可能出现什么问题,只是不要这样做.

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

  • 我还看到每次让线程同时执行“add()”时都会添加“null”。 (2认同)

Sha*_*mik 5

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


Vis*_*ohn 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

因此,可以证明ArrayList的并发更新不会给出正确的结果。

如果我将ArrayList替换为Synchronized版本,则程序将输出正确的结果。