我们在add(obj)
上有多个线程调用ArrayList
。
我的理论是,当add
被两个线程同时调用时,添加的两个对象中只有一个实际上被添加到ArrayList
。这有可能吗?
如果是这样,你怎么解决这个问题?使用像Vector
?
答案 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)
您还可以获得null
,ArrayOutOfBoundsException
或其他与实施相关的内容。已经观察到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
对我来说更可取,因为它是: