如何创建并发List实例,我可以通过索引访问元素? JDK是否有我可以使用的任何类或工厂方法?
答案 0 :(得分:148)
java.util.concurrent中有一个并发列表实现。特别是CopyOnWriteArrayList。
答案 1 :(得分:133)
如果您不关心基于索引的访问权限并且只想要List的插入顺序保留特性,则可以考虑java.util.concurrent.ConcurrentLinkedQueue。由于它实现了Iterable,一旦你完成了所有项目的添加,你就可以使用增强的for语法循环遍历内容:
Queue<String> globalQueue = new ConcurrentLinkedQueue<String>();
//Multiple threads can safely call globalQueue.add()...
for (String href : globalQueue) {
//do something with href
}
答案 2 :(得分:115)
如果你需要的只是简单的调用同步,你可以很好地使用Collections.synchronizedList(List):
List<Object> objList = Collections.synchronizedList(new ArrayList<Object>());
答案 3 :(得分:40)
因为获取位置并从给定位置获取元素的行为自然需要一些锁定(您不能让列表在这两个操作之间进行结构更改)。
并发集合的想法是每个操作本身都是原子操作,可以在没有显式锁定/同步的情况下完成。
因此,在预期进行并发访问的情况下,将元素从给定的n
作为原子操作从位置List
获取并没有多大意义。
答案 4 :(得分:1)
CopyOnWriteArrayList是ArrayList的线程安全变体,其中 所有可变操作(添加,设置等)都由 制作基础数组的新副本。
CopyOnWriteArrayList是同步List实现List接口及其java.util.concurrent包的一部分及其线程安全集合的并发替代方法。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
CopyOnWriteArrayList是故障安全的,并且在迭代过程中使用单独的ArrayList副本修改基础CopyOnWriteArrayList时不会引发ConcurrentModificationException。
这通常太昂贵了,因为复制数组涉及每个更新操作,将创建克隆副本。仅在频繁读取操作时,CopyOnWriteArrayList是最佳选择。
/**
* Returns a shallow copy of this list. (The elements themselves
* are not copied.)
*
* @return a clone of this list
*/
public Object clone() {
try {
@SuppressWarnings("unchecked")
CopyOnWriteArrayList<E> clone =
(CopyOnWriteArrayList<E>) super.clone();
clone.resetLock();
return clone;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
答案 5 :(得分:1)
您有以下选择:
Collections.synchronizedList()
:您可以包装任何List
实现(ArrayList
,LinkedList
或第三方列表)。使用synchronized
将保护对每种方法(读写)的访问。使用iterator()
或增强型for循环时,必须手动进行同步;在迭代时,其他线程甚至被完全阻止读取。您还可以分别为每个hasNext
和next
调用进行同步,但是可以进行ConcurrentModificationException
。
CopyOnWriteArrayList
:修改成本很高,但无需等待阅读。迭代器从不抛出ConcurrentModificationException
,它们在创建迭代器时返回列表的快照,即使列表在迭代时被另一个线程修改了。对于不经常更新的列表很有用。首选使用addAll
之类的批量操作进行更新-内部数组的复制次数较少。
Vector
:非常类似于synchronizedList
,但是迭代也是同步的。但是,如果向量被另一个线程修改,则迭代器可以抛出ConcurrentModificationException
。
其他选项:
Collections.unmodifiableList()
:无锁,线程安全但不可修改Queue
或Deque
。没有索引访问,也没有在任意位置进行添加/删除的操作。他们有多个并发实现,它们具有更好的性能和更好的并发访问,但这超出了此问题的范围。您还可以查看JCTools,它们包含更多针对单个使用者或单个生产者的性能更高的队列实现。答案 6 :(得分:0)
如果您从未计划从列表中删除元素(因为这需要更改已删除元素之后的所有元素的索引),则可以使用ConcurrentSkipListMap<Integer, T>
代替ArrayList<T>
,例如>
NavigableMap<Integer, T> map = new ConcurrentSkipListMap<>();
这将允许您按以下方式将项目添加到“列表”的末尾,只要只有一个编写者线程(否则map.size()
之间存在竞争条件和map.put()
):
// Add item to end of the "list":
map.put(map.size(), item);
您还可以通过简单地调用map.put(index, item)
来明显地修改“列表”(即地图)中任何项目的值。
将项目放入地图或通过索引检索项目的平均成本为O(log(n)),并且ConcurrentSkipListMap
是无锁的,这比说Vector
更好( ArrayList
的旧同步版本。
您可以使用NavigableMap
界面的方法在“列表”中来回循环。
您可以将以上所有内容包装到实现List
接口的类中,只要您了解竞争条件警告(或者您可以仅同步writer方法),并且您需要抛出一个remove
方法不受支持的操作异常。实现所有必需的方法需要很多样板,但这是实现的快速尝试。
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentSkipListMap;
public class ConcurrentAddOnlyList<V> implements List<V> {
private NavigableMap<Integer, V> map = new ConcurrentSkipListMap<>();
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean contains(Object o) {
return map.values().contains(o);
}
@Override
public Iterator<V> iterator() {
return map.values().iterator();
}
@Override
public Object[] toArray() {
return map.values().toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return map.values().toArray(a);
}
@Override
public V get(int index) {
return map.get(index);
}
@Override
public boolean containsAll(Collection<?> c) {
return map.values().containsAll(c);
}
@Override
public int indexOf(Object o) {
for (Entry<Integer, V> ent : map.entrySet()) {
if (Objects.equals(ent.getValue(), o)) {
return ent.getKey();
}
}
return -1;
}
@Override
public int lastIndexOf(Object o) {
for (Entry<Integer, V> ent : map.descendingMap().entrySet()) {
if (Objects.equals(ent.getValue(), o)) {
return ent.getKey();
}
}
return -1;
}
@Override
public ListIterator<V> listIterator(int index) {
return new ListIterator<V>() {
private int currIdx = 0;
@Override
public boolean hasNext() {
return currIdx < map.size();
}
@Override
public V next() {
if (currIdx >= map.size()) {
throw new IllegalArgumentException(
"next() called at end of list");
}
return map.get(currIdx++);
}
@Override
public boolean hasPrevious() {
return currIdx > 0;
}
@Override
public V previous() {
if (currIdx <= 0) {
throw new IllegalArgumentException(
"previous() called at beginning of list");
}
return map.get(--currIdx);
}
@Override
public int nextIndex() {
return currIdx + 1;
}
@Override
public int previousIndex() {
return currIdx - 1;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void set(V e) {
// Might change size of map if currIdx == map.size(),
// so need to synchronize
synchronized (map) {
map.put(currIdx, e);
}
}
@Override
public void add(V e) {
synchronized (map) {
// Insertion is not supported except at end of list
if (currIdx < map.size()) {
throw new UnsupportedOperationException();
}
map.put(currIdx++, e);
}
}
};
}
@Override
public ListIterator<V> listIterator() {
return listIterator(0);
}
@Override
public List<V> subList(int fromIndex, int toIndex) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean add(V e) {
synchronized (map) {
map.put(map.size(), e);
return true;
}
}
@Override
public boolean addAll(Collection<? extends V> c) {
synchronized (map) {
for (V val : c) {
add(val);
}
return true;
}
}
@Override
public V set(int index, V element) {
synchronized (map) {
if (index < 0 || index > map.size()) {
throw new IllegalArgumentException("Index out of range");
}
return map.put(index, element);
}
}
@Override
public void clear() {
synchronized (map) {
map.clear();
}
}
@Override
public synchronized void add(int index, V element) {
synchronized (map) {
if (index < map.size()) {
// Insertion is not supported except at end of list
throw new UnsupportedOperationException();
} else if (index < 0 || index > map.size()) {
throw new IllegalArgumentException("Index out of range");
}
// index == map.size()
add(element);
}
}
@Override
public synchronized boolean addAll(
int index, Collection<? extends V> c) {
synchronized (map) {
if (index < map.size()) {
// Insertion is not supported except at end of list
throw new UnsupportedOperationException();
} else if (index < 0 || index > map.size()) {
throw new IllegalArgumentException("Index out of range");
}
// index == map.size()
for (V val : c) {
add(val);
}
return true;
}
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public V remove(int index) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
}
请不要忘记,即使使用了如上所述的编写器线程同步,也要注意不要遇到可能导致您丢弃项目的竞争条件,例如,如果您尝试遍历读取器中的列表,编写器线程添加到列表末尾时的线程。
您甚至可以将ConcurrentSkipListMap
用作双端列表,只要您不需要每个项目的键即可表示列表中的实际位置(即添加到列表的开头将分配项目否定键)。 (此处适用相同的比赛条件警告,即应该只有一个编写线程。)
// Add item after last item in the "list":
map.put(map.isEmpty() ? 0 : map.lastKey() + 1, item);
// Add item before first item in the "list":
map.put(map.isEmpty() ? 0 : map.firstKey() - 1, item);
答案 7 :(得分:-1)
通常,如果需要并发列表,则它位于模型对象内(因为您不应该使用抽象数据类型(例如,列表来表示应用程序模型图中的节点)),或者它是特定服务的一部分,因此可以进行同步自己访问。
class MyClass {
List<MyType> myConcurrentList = new ArrayList<>();
void myMethod() {
synchronzied(myConcurrentList) {
doSomethingWithList;
}
}
}
通常这足以让您前进。如果需要迭代,请迭代列表的副本,而不是列表本身,而只同步复制列表的部分,而无需在其上进行迭代。
当同时处理一个列表时,通常除了添加,删除或复制外,您还需要做更多的事情,这意味着该操作变得足够有意义以警告其自己的方法,并且该列表成为一个特殊类的成员,该类仅代表该特定列表,并且线程安全行为。
即使我同意需要并发列表实现,并且Vector / Collections.sychronizeList(list)也不起作用,以确保您需要使用compareAndAdd或compareAndRemove或get(...,ifAbsentDo)之类的东西,即使您有一个ConcurrentList实现,开发人员经常会在不考虑并发列表(和映射)时真正考虑什么是事务的情况下引入错误。
在这些情况下,事务太小,不足以与并发ADT(抽象数据类型)进行交互的预期目的,总是导致我将列表隐藏在特殊的类中,并使用已同步的对象来同步访问该类对象的方法在方法层面上这是确保交易正确的唯一方法。
我已经看到太多错误,无法以其他任何方式执行-至少在代码很重要并且能够处理诸如金钱或安全之类的东西或保证某种服务质量措施(例如至少一次且仅发送一次消息)的情况下。 / p>