web-dev-qa-db-fra.com

Ajout de threads simultanés à ArrayList en même temps - que se passe-t-il?

Nous avons plusieurs threads appelant add(obj) sur un ArrayList.

Ma théorie est que lorsque add est appelé simultanément par deux threads, seul un des deux objets ajoutés est réellement ajouté au ArrayList. Est-ce plausable?

Si oui, comment contournez-vous cela? Utiliser une collection synchronisée comme Vector?

47
Marcus Leon

Il n'y a aucun comportement garanti pour ce qui se passe lorsque add est appelé simultanément par deux threads sur ArrayList. Cependant, d'après mon expérience, les deux objets ont bien été ajoutés. La plupart des problèmes de sécurité des threads liés aux listes traitent de l'itération lors de l'ajout/de la suppression. Malgré cela, je déconseille fortement d'utiliser Vanilla ArrayList avec plusieurs threads et un accès simultané.

Le vecteur était la norme pour les listes simultanées, mais maintenant la norme consiste à utiliser la Liste synchronisée des collections .

Aussi, je recommande fortement Java Concurrence in Practice by Goetz et al si vous allez passer du temps à travailler avec des threads en Java. Le livre couvre ce problème avec beaucoup plus de détails.

48
derivation

Un certain nombre de choses pourraient se produire. Vous pourriez obtenir les deux objets ajoutés correctement. Vous ne pouvez ajouter qu'un seul des objets. Vous pouvez obtenir une exception ArrayIndexOutOfBounds car la taille du tableau sous-jacent n'a pas été ajustée correctement. Ou d'autres choses peuvent arriver. Il suffit de dire que vous ne pouvez pas compter sur un comportement quelconque.

Comme alternatives, vous pouvez utiliser Vector, vous pouvez utiliser Collections.synchronizedList, vous pouvez utiliser CopyOnWriteArrayList, ou vous pouvez utiliser un verrou séparé. Tout dépend de ce que vous faites d'autre et du type de contrôle que vous avez sur l'accès à la collection.

15

Vous pouvez également obtenir un null, un ArrayOutOfBoundsException, ou quelque chose à laisser à l'implémentation. HashMaps ont été observés pour entrer dans une boucle infinie dans les systèmes de production. Vous n'avez pas vraiment besoin de savoir ce qui pourrait mal se passer, mais ne le faites pas.

Vous pouvez utiliser Vector, mais cela a tendance à fonctionner, l'interface n'est pas assez riche. Vous constaterez probablement que vous voulez une structure de données différente dans la plupart des cas.

8

J'ai trouvé le code suivant pour imiter quelque peu un scénario du monde réel.

100 tâches sont exécutées en parallèle et mettent à jour leur état terminé dans le programme principal. J'utilise un CountDownLatch pour attendre la fin de la tâche.

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();
        }
    }
}

Lorsque j'ai exécuté l'application 10 fois et au moins 3 à 4 fois, le programme n'a pas imprimé le nombre correct de tâches terminées. Idéalement, il devrait imprimer 100 (si aucune exception ne se produit). Mais dans certains cas, il imprimait 98, 99, etc.

Ainsi, cela prouve que les mises à jour simultanées d'ArrayList ne donneront pas de résultats corrects.

Si je remplace la ArrayList par une version Synchronized, le programme sort les résultats corrects.

5
Vishal John

vous pouvez utiliser List l = Collections.synchronizedList(new ArrayList()); si vous voulez une version thread-safe de arrayList.

3
Shamik

Le comportement n'est probablement pas défini car ArrayList n'est pas threadsafe. Si vous modifiez la liste alors qu'un itérateur interagit dessus, vous obtiendrez une exception ConcurrentModificationException. Vous pouvez encapsuler ArrayList avec Collection.synchronizedList ou utiliser une collection thread-safe (il y en a beaucoup), ou simplement placer les appels d'ajout dans un bloc synchronisé.

3
Robby Pond

Java.util.concurrent a une liste de tableaux thread-safe. La liste ArrayList standard n'est pas adaptée aux threads et le comportement lorsque plusieurs threads sont mis à jour en même temps n'est pas défini. Il peut également y avoir des comportements étranges avec plusieurs lecteurs lorsqu'un ou plusieurs threads écrivent en même temps.

1
Tom Cabanski

Vous pouvez utiliser à la place de ArrayList();:

Collections.synchronizedList( new ArrayList() );

ou

new Vector();

synchronizedList selon moi préférable car c'est:

  • plus rapide sur 50-100%
  • peut fonctionner avec des ArrayList déjà existants
0
Stan Fad

http://Java.Sun.com/j2se/1.4.2/docs/api/Java/util/ArrayList.html

Notez que cette implémentation n'est pas synchronisée. Si plusieurs threads accèdent simultanément à une instance ArrayList et qu'au moins l'un des threads modifie la liste structurellement, elle doit être synchronisée en externe.

Puisqu'il n'y a pas de synchronisation interne, ce que vous théorisez n'est pas plausible.

Ainsi, les choses se désynchronisent, avec des résultats désagréables et imprévisibles.

0
WhirlWind