我们有多个线程调用add(obj)
在ArrayList
。并发线程同时添加到ArrayList - 会发生什么?
我的理论是,当两个线程同时调用add
时,只有一个被添加的对象真的被添加到ArrayList
。这是可行的吗?
如果是这样,你如何解决这个问题?使用同步收集,如Vector
?
我们有多个线程调用add(obj)
在ArrayList
。并发线程同时添加到ArrayList - 会发生什么?
我的理论是,当两个线程同时调用add
时,只有一个被添加的对象真的被添加到ArrayList
。这是可行的吗?
如果是这样,你如何解决这个问题?使用同步收集,如Vector
?
对ArrayList上的两个线程同时调用add时发生的情况没有保证行为。但是,我的经验是两个对象都被添加了。与列表相关的大多数线程安全问题在添加/删除时处理迭代。尽管如此,我强烈建议不要使用具有多线程和并发访问的vanilla ArrayList。
向量曾经是并发列表的标准,但现在的标准是使用Collections synchronized list。
另外,如果您打算随时用Java中的线程工作,我强烈推荐Goetz等人在实践中使用Java Concurrency。本书更详细地介绍了这个问题。
如果你想要线程安全的arrayList,你可以使用List l = Collections.synchronizedList(new ArrayList());
。
http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html
注意,此实现不是同步的。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了列表,则它必须在外部同步。
由于内部没有同步,所以你推理的东西并不合理。
因此,事情变得不同步,带来不愉快和不可预知的结果。
对,看到了。我曾经想过会有一个'ConcurrentModificationException'会被抛出,这对我们来说并没有发生..也许我们遇到了另一个不使用线程安全集合的影响.. – 2010-04-26 18:52:17
@Marcus似乎只是如果你在迭代它的同时修改列表。此外,不保证此异常将被抛出。 – WhirlWind 2010-04-26 18:53:51
java.util.concurrent有一个线程安全的数组列表。标准ArrayList不是线程安全的,并且多线程同时更新时的行为未定义。当一个或多个线程同时写入时,多读者也可能会出现奇怪的行为。
你也可以得到一个null
,一个ArrayOutOfBoundsException
,或一些遗留下来的实施。已经观察到在生产系统中进入无限循环。你不需要知道什么可能会出错,只是不要这样做。
您可以使用Vector
,但它倾向于解决接口不够丰富。在大多数情况下,你可能会发现你想要一个不同的数据结构。
该行为可能是未定义的,因为ArrayList不是线程安全的。如果在Iterator进行交互时修改列表,则会得到ConcurrentModificationException。您可以使用Collection.synchronizedList封装ArrayList,或使用线程安全的集合(有很多),或者将add调用放入同步块中。
任何数量的事情都可能发生。您可以正确添加这两个对象。您只能添加其中一个对象。你可能会得到一个ArrayIndexOutOfBounds异常,因为底层数组的大小没有适当调整。或者可能发生其他事情。只要说你不能依赖任何发生的行为就够了。
作为替代方案,您可以使用Vector
,您可以使用Collections.synchronizedList
,您可以使用CopyOnWriteArrayList
,或者您可以使用单独的锁。这一切都取决于你在做什么以及对访问该集合有何种控制。
我想出了下面的代码来模仿一些真实的世界场景。
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,程序输出正确的结果。
你也可以使用的ArrayList();
:
Collections.synchronizedList(new ArrayList());
或
new Vector();
synchronizedList
因为我最好的,因为它是:
“这是我的经验,两个对象已被添加罚款”只是想指出,这只是运气。 ArrayList数据损坏问题的窗口可能非常小,但它仍然存在 – 2010-04-26 20:32:23
对,这就是我的观点。绝大多数情况下不会有问题;但我们不会对大多数情况编程。所以我强烈建议不要使用ArrayList。 – derivation 2010-04-26 21:03:02