Si vous ne prévoyez jamais de supprimer des éléments de la liste (car cela nécessite de modifier l'index de tous les éléments après l'élément supprimé), vous pouvez utiliser la fonction ConcurrentSkipListMap<Integer, T>
à la place de ArrayList<T>
par exemple
NavigableMap<Integer, T> map = new ConcurrentSkipListMap<>();
Cela vous permettra d'ajouter des éléments à la fin de la "liste" comme suit, tant qu'il n'y a qu'un seul fil d'écriture (sinon il y a une condition de course entre map.size()
y map.put()
) :
// Add item to end of the "list":
map.put(map.size(), item);
Vous pouvez aussi évidemment modifier la valeur de n'importe quel élément de la "liste" (c'est-à-dire de la carte) en appelant tout simplement map.put(index, item)
.
Le coût moyen pour placer des éléments dans la carte ou les récupérer par index est O(log(n)), et ConcurrentSkipListMap
est libre de tout verrou, ce qui le rend nettement meilleur que, par exemple, le programme Vector
(l'ancienne version synchronisée de ArrayList
).
Vous pouvez itérer en avant et en arrière dans la "liste" en utilisant les méthodes de l'objet NavigableMap
interface.
Vous pourriez regrouper tout ce qui précède dans une classe qui implémente la fonction List
pour autant que vous compreniez les avertissements relatifs aux conditions de course (ou vous pourriez synchroniser uniquement les méthodes d'écriture) - et vous devriez lever une exception d'opération non prise en charge pour l'interface remove
méthodes. L'implémentation de toutes les méthodes requises nécessite un certain nombre d'éléments standard, mais voici une tentative rapide d'implémentation.
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();
}
}
N'oubliez pas que, même avec la synchronisation du fil d'écriture comme indiqué ci-dessus, vous devez veiller à ne pas créer de conditions de course qui pourraient vous faire perdre des éléments, si par exemple vous essayez d'itérer dans une liste dans un fil de lecture pendant qu'un fil d'écriture ajoute à la fin de la liste.
Vous pouvez même utiliser ConcurrentSkipListMap
comme une liste à deux extrémités, tant que vous n'avez pas besoin que la clé de chaque élément représente la position réelle dans la liste (c'est-à-dire que l'ajout au début de la liste attribuera aux éléments des clés négatives). (La même mise en garde concernant les conditions de course s'applique ici, c'est-à-dire qu'il ne doit y avoir qu'un seul fil d'écriture).
// 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);
30 votes
Pourquoi pas constructif? Plusieurs ont proposé CopyOnWriteArrayList qui n'est pas trouvée dans .Net. Vous pouvez dire que les deux questions sont liées mais ne pas fermer celle-ci !!!
0 votes
Dans .NET, l'équivalent de CopyOnWriteArrayList est Immutable Collections. Une collection concurrente est une collection qui ne nécessite pas de verrouillage pour être modifiée (comme ConcurrentQueue, etc.), et NON pas une collection qui copie son contenu à chaque fois qu'elle est copiée.
1 votes
Je n'ai aucune idée de pourquoi Jarrod Roberson penserait que ce serait une bonne idée de prendre les modifications détaillées apportées par Stephan et de les ramener à la question originale, mal formulée. La réponse de Jarrod reste néanmoins parfaitement acceptable. En fait, CopyOnWriteArrayList est la seule classe concurrente implémentant List dans le JDK. Perplexe...
13 votes
Parce que la réponse acceptée était à la question d'origine et que Stephan a posé une question complètement non liée avec un tas de code source que le posteur original n'a inclus nulle part changeant complètement la question, ce qui a généré plus de réponses suggérant des choses autres que la
Liste
que l'original dit spécifiquement être une exigence ce qui est considéré comme du vandalisme. Un modérateur a déjà verrouillé la question en raison des personnes qui se plaignent que les réponses ne répondent pas à cette version vandalisée de la question.1 votes
/code>verrouillé/
fermé
/ commentaire précédent11 votes
Il n'y a aucune raison que cette question soit fermée. Elle concerne les classes dans le JDK, ce qui n'a rien à voir avec la recherche d'une bibliothèque ; c'est la base de Java.