并发线程同时添加到ArrayList – 会发生什么?
我们有多个线程调用ArrayList
上的add(obj)
。
我的理论是,当两个线程同时调用add
时,只有两个被添加的对象中的一个真的被添加到ArrayList
。 这是可信的吗?
如果是这样,你怎么解决这个问题? 使用像Vector
这样的同步集合?
当ArrayList上的两个线程同时调用add时,会发生什么情况没有保证。 但是,我的经验是,两个对象都被罚款。 与列表相关的大多数线程安全问题在添加/删除时处理迭代。 尽pipe如此,我强烈build议不要使用具有multithreading和并发访问的vanilla ArrayList。
向量曾经是并发列表的标准,但现在标准是使用集合同步列表 。
另外,如果您将要花费任何时间使用Java中的线程,我强烈推荐Goetz等人的Java Concurrency in Practice。 本书更详细地介绍了这个问题。
任何事情都可能发生。 您可以正确添加两个对象。 你只能得到一个添加的对象。 你可能会得到一个ArrayIndexOutOfBoundsexception,因为底层数组的大小没有被正确调整。 或者可能发生其他事情。 只要说你不能依靠发生的任何行为就够了。
作为select,你可以使用Vector
,你可以使用Collections.synchronizedList
,你可以使用CopyOnWriteArrayList
,或者你可以使用一个单独的锁。 这一切都取决于你在做什么,以及你有什么样的控制访问收集。
你也可以得到一个null
,一个ArrayOutOfBoundsException
,或者执行的东西。 已经观察到HashMap
在生产系统中进入无限循环。 你真的不需要知道什么可能出错,只是不要这样做。
你可以使用Vector
,但是它倾向于解决接口不够丰富的问题。 在大多数情况下,你可能会发现你想要一个不同的数据结构。
行为可能是未定义的,因为ArrayList不是线程安全的。 如果在迭代器正在进行迭代时修改列表,则会得到一个ConcurrentModificationExceptionexception。 您可以使用Collection.synchronizedList封装ArrayList,也可以使用线程安全的集合(有很多),或者只将add调用放入同步块中。
你可以使用List l = Collections.synchronizedList(new ArrayList());
如果你想要线程安全版本的arrayList。
我想出了下面的代码来模拟一个真实世界的场景。
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次,至less3到4次程序没有打印正确的完成任务数量。 理想情况下,它应该打印100(如果没有例外发生)。 但在某些情况下,它打印98,99等
因此,它certificate了ArrayList的并发更新不会给出正确的结果。
如果我用同步版本replaceArrayList,程序输出正确的结果。
java.util.concurrent有一个线程安全的数组列表。 标准ArrayList不是线程安全的,并且multithreading同时更新时的行为是未定义的。 当一个或多个线程同时写入时,多个阅读器也可能会有奇怪的行为。
http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html
请注意,此实现不同步。 如果多个线程同时访问ArrayList实例,并且至less有一个线程在结构上修改了列表,则它必须在外部进行同步。
由于内部没有同步,所以你的理论是不合理的。
所以,事情变得不同步,带来不愉快和不可预测的结果。
你可以使用而不是ArrayList();
:
Collections.synchronizedList( new ArrayList() );
要么
new Vector();
我更喜欢synchronizedList
,因为它是:
- 更快的50-100%
- 可以使用已经存在的ArrayList的