1
votes

Java - ArrayList synchronisé toujours ConcurrentModificationException

J'ai un final ArrayList routingTable = new ArrayList (); auquel on accède plusieurs fois. Mais je n'obtiens qu'à un moment donné une ConcurrentModificationException qui se trouve dans le fil de discussion suivant:

Thread checkReplies = new Thread(() -> {

    while (true) {

        synchronized (routingTable) {

            for (RoutingTableEntry entry : routingTable) { // throws it here

                // do smth
            }
        }

        // [...]
    }
 });
 checkReplies.start();

Il jette l'exception à la boucle même si le routingTable code> est déjà synchronisé. Ce fil n'est exécuté qu'une seule fois par classe.

Des idées?


7 commentaires

Ajoutez / supprimez-vous des éléments dans routingTable dans le cycle for ?


Y a-t-il une raison particulière pour laquelle vous n'utilisez pas uniquement Collections.synchronizedList () ou CopyOnWriteArrayList ?


@Joe itérer une Collections.synchronizedList n'est pas la même chose que l'itération d'une liste dans un bloc synchronisé.


@GabrieleMariotti j'appelle une méthode qui supprime alors (dois-je faire la méthode synchronisée?)


@Stefanxyz C'est le problème. Vous ne pouvez pas le faire.


@Joe je ne savais pas que cela existait


@GabrieleMariotti puis-je appeler une méthode synchronisée qui modifie ensuite la liste? (Même si alors toute la déclaration synchronisée est (je pense du moins) inutile)


3 Réponses :


2
votes

Il y a deux possibilités:

  1. Vous avez autre code dans la classe qui modifie routingTable , et n'utilise pas synchronized (routingTable) pour ce faire . Ainsi, lorsque l'autre code modifie la liste pendant cette itération, vous obtenez l'erreur.

  2. Vous modifiez la liste dans laquelle vous avez le commentaire "do smth". Ce n'est pas parce que vous avez synchronisé la liste que vous pouvez la modifier tout en effectuant une boucle avec son itérateur. Vous ne pouvez pas (sauf via l'itérateur lui-même, ce qui signifierait que vous ne pouvez pas utiliser la boucle améliorée for ). (Parfois, vous vous en sortez à cause des détails de l'implémentation de ArrayList , mais d'autres fois vous ne le faites pas.)

Voici un exemple de # 2 ( live copy ):

for (var it = routingTable.listIterator(); it.hasNext; ) {
    var entry = it.next();
    if (/*...some condition...*/) {
        it.remove(); // Removes the current entry
    }
}

Cela échoue avec l'implémentation par JDK12 de ArrayList (au moins, probablement d'autres).

Une chose clé à comprendre est que la synchronisation et la modification de la liste pendant l'itération sont des concepts largement indépendants. La synchronisation (effectuée correctement) empêche plusieurs threads d'accéder à la liste en même temps. Mais comme vous pouvez le voir dans l'exemple ci-dessus, un seul thread peut provoquer une ConcurrentModificationException en modifiant la liste pendant l'itération. Ils concernent uniquement le fait que si vous avez un thread lisant la liste et un autre thread qui peut la modifier, la synchronisation empêche la modification pendant la lecture. À part cela, ils n'ont aucun rapport.

Dans un commentaire, vous avez dit:

j'appelle une méthode qui supprime alors

Si vous supprimez l'entrée de la boucle, vous pouvez le faire via un list iterator méthode remove :

var routingTable = new ArrayList<String>();
routingTable.add("one");
routingTable.add("two");
routingTable.add("three");
synchronized (routingTable) {
    for (String entry : routingTable) {
        if (entry.equals("two")) {
            routingTable.add("four");
        }
    }
}

(Il existe également des opérations add et set .)


3 commentaires

Je ne sais pas trop ce que vous entendez par "Vous ne pouvez pas (sauf ...)". Avez-vous oublié de terminer la phrase ou ne pouvez-vous rien changer de la liste dans l'instruction synchronisée?


@Stefanxyz - La phrase est complète, list iterators ont des méthodes de modification que vous pouvez utiliser en toute sécurité pendant l'itération. Pour les utiliser, cependant, vous devez utiliser explicitement listIterator et un standard pour ou tandis que ou une boucle similaire, vous ne pouvez pas utiliser la boucle améliorée Boucle for (car elle vous cache l'itérateur). Voir aussi les ajouts que j'ai apportés à la réponse: vous confondez deux choses qui ne sont en grande partie pas liées (synchronisation et modification simultanée).


Merci d'avoir répondu. Je l'ai déjà réalisé grâce à Gabriele Mariotti et tevemadar. J'ai retravaillé la structure en une boucle «pour» normale qui le rend alors possible (réduction de l'index par des changements de liste)



1
votes

ConcurrentModificationException n'est pas nécessairement «concurrente» dans le sens du filetage, elle peut être «concurrente» dans le sens où vous ne devez pas modifier directement une collection en même temps lorsque vous itérez ça.

Il est également dans la documentation depuis longtemps (extrait de Java7: https://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html )

Notez que cette exception n'indique pas toujours qu'un objet a été modifié simultanément par un thread différent. Si un seul thread émet une séquence d'appels de méthode qui viole le contrat d'un objet, l'objet peut lever cette exception. Par exemple, si un thread modifie une collection directement pendant qu'il itère sur la collection avec un itérateur rapide, l'itérateur lèvera cette exception.

Et for (x: y) utilise un itérateur, qui peut facilement finir par être un "fail-fast".


0 commentaires

0
votes

À partir de votre commentaire dans la question d'origine, vous supprimez des éléments dans le routingTable pendant que vous effectuez une itération .

Vous ne peut pas le faire (même si le routingTable est synchronisé)

for (RoutingTableEntry entry : routingTable) { // throws it here
     // routingTable.remove(....);
}


1 commentaires

merci, je pensais en fait que c'était simultané (@tevemadar l'a mentionné) et cela a maintenant du sens.