我有一个在多线程环境中使用的并发List。构建List后,大多数操作都会遍历它。我想知道以下哪两种方法更有效,或者创建一个新的List vs使用synchronized的成本是多少?或者还有其他更好的方法?
List<Object> list = new CopyOnWriteArrayList<Object>();
public int[] getAllValue1() {
List<Object> list2 = new ArrayList<Object>(list);
int[] data = new int[list2.size()];
int i = 0;
for (Object obj : list2) {
data[i++] = obj.getValue();
}
return data;
}
public int[] getAllValue2() {
synchronized (list) {
int[] data = new int[list.size()];
int i = 0;
for (Object obj : list) {
data[i++] = obj.getValue();
}
return data;
}
}
更新 getAllValue1():它是线程安全的,因为它需要CopyOnWriteList的快照,它本身就是线程安全List。然而,正如Sharakan指出的那样,成本是迭代2列表,并创建一个本地对象ArrayList,如果原始列表很大,这可能是昂贵的。
getAllValue2():它在同步块中也是线程安全的。 (假设其他函数正确地进行同步。)将它放入同步块的原因是因为我想预先分配数组,以确保.size()调用与迭代同步。 (迭代部分是线程安全的,因为它使用CopyOnWriteList。)但是这里的成本是使用syncrhonized块的机会成本。如果有100万个客户端调用getAllValue2(),则每个客户端都必须等待。
所以我猜答案实际上取决于有多少并发用户需要读取数据。如果并发用户不多,那么方法2可能更好。否则,method1更好。同意?
在我的使用中,我有几个并发客户端,可能method2是首选。 (顺便说一句,我的名单大约是10k)。
答案 0 :(得分:0)
修改(再次):
因为你在写数组列表上使用了一个副本(应该更加观察),我会得到迭代器并使用它来初始化你的数组。由于迭代器是您请求它时数组的快照,因此您可以在列表上进行同步以获取大小,然后进行迭代而不必担心ConcurrentModificationException
或更改迭代器。
public int[] getAllValue1() {
synchronized(list){
int[] data = new int[list.size()];
}
Iterator i = list.iterator();
while(i.hasNext()){
data[i++] = i.next().getValue();
}
return data;
}
答案 1 :(得分:0)
getAllValue1看起来很好,因为你需要根据对象的一个字段返回一个基本类型数组。它将是两次迭代,但是一致并且您不会在读取器线程之间引起任何争用。您尚未发布任何分析结果,但除非您的列表非常大,否则我会更担心多线程环境中的争用而不是两次完整迭代的成本。
如果更改API,则可以删除一次迭代。最简单的方法是返回一个Collection,如下所示:
public Collection<Integer> getAllValue1() {
List<Integer> list2 = new ArrayList<Integer>(list.size());
for (Object obj : list2) {
list2.add(obj.getValue());
}
return list2;
}
如果您可以通过这种方式更改API,那将是一项改进。
答案 2 :(得分:0)
我认为第二个更有效率。原因是,在第一个中,您创建另一个列表作为本地创建。这意味着如果原始列表包含大量数据,它将复制所有数据。如果它包含数百万个数据,那么这将是一个问题。
但是,有list.toArray()
方法
Collections界面还包含一些有用的东西
Collection synchronizedCollection = Collections.synchronizedCollection(list);
或
List synchronizedList = Collections.synchronizedList(list);
如果您需要VALUE对象,而不是对象,请使用您的第二个代码移动。另外,你可以用上面的函数替换第二个代码的适当部分,并做任何你想做的事。