Sobes.tech
Middle

Πώς να αποφύγετε την εξαίρεση ConcurrentModificationException κατά την επανάληψη μιας συλλογής;

sobes.tech AI

Απάντηση από AI

Για να αποφύγετε το ConcurrentModificationException κατά την επανάληψη σε μια συλλογή, μπορείτε να χρησιμοποιήσετε τις ακόλουθες προσεγγίσεις:

  1. Χρήση ασφαλών για νήματα συλλογών: Το java.util.concurrent παρέχει εναλλακτικές λύσεις ασφαλείς για νήματα για τις τυπικές συλλογές. Για παράδειγμα:

    • CopyOnWriteArrayList αντί για ArrayList
    • CopyOnWriteArraySet αντί για HashSet
    • ConcurrentHashMap αντί για HashMap
    // Παράδειγμα με CopyOnWriteArrayList
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.Iterator;
    
    public class ConcurrentCollectionExample {
        public static void main(String[] args) {
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            // Ο iterator δεν ρίχνει `ConcurrentModificationException`
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Η τροποποίηση της συλλογής κατά τη διάρκεια της επανάληψης είναι ασφαλής
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Τελική λίστα: " + list);
        }
    }
    
  2. Κλείδωμα της συλλογής: Συγχρονίστε την πρόσβαση στη συλλογή χρησιμοποιώντας synchronized ή μπλοκ κλειδώματος java.util.concurrent.locks.

    // Παράδειγμα με συγχρονισμένο μπλοκ
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class SynchronizedCollectionExample {
       public static void main(String[] args) {
           List<String> list = new ArrayList<>();
           list.add("A");
           list.add("B");
           list.add("C");
    
           synchronized (list) { // Κλείδωμα της συλλογής
               Iterator<String> iterator = list.iterator();
               while (iterator.hasNext()) {
                   String element = iterator.next();
                   System.out.println(element);
                   // Δεν επιτρέπεται η τροποποίηση της συλλογής εδώ αν άλλος νήμας έχει πρόσβαση
               }
           }
           // Η τροποποίηση της συλλογής εκτός του συγχρονισμένου μπλοκ ή από άλλο νήμα
           // μπορεί ακόμα να προκαλέσει `ConcurrentModificationException` κατά την παράλληλη επανάληψη
       }
    }
    
  3. Χρήση iterator για διαγραφή στοιχείων: Αν χρειάζεται να διαγράψετε στοιχεία κατά τη διάρκεια της επανάληψης, χρησιμοποιήστε τη μέθοδο remove() του iterator (αν υποστηρίζεται από τη συλλογή).

    // Παράδειγμα διαγραφής με Iterator.remove()
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class IteratorRemoveExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Ασφαλής διαγραφή στοιχείου μέσω iterator
                if (element.equals("B")) {
                    iterator.remove();
                }
            }
            System.out.println("Τελική λίστα: " + list);
        }
    }
    
  4. Επανάληψη σε αντίγραφο της συλλογής: Δημιουργήστε ένα αντίγραφο της συλλογής και επαναλάβετε πάνω σε αυτό. Οι αλλαγές στην αρχική συλλογή δεν θα επηρεάσουν την επανάληψη.

    // Παράδειγμα επανάληψης σε αντίγραφο
    import java.util.ArrayList;
    import java.util.List;
    
    public class CopyIterationExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            // Δημιουργία αντιγράφου για επανάληψη
            List<String> copyList = new ArrayList<>(list);
    
            for (String element : copyList) {
                System.out.println(element);
                // Μπορούμε να τροποποιήσουμε την αρχική συλλογή κατά τη διάρκεια της επανάληψης του αντιγράφου
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Τελική λίστα: " + list);
        }
    }
    

Η επιλογή της προσέγγισης εξαρτάται από τις συγκεκριμένες απαιτήσεις: χρειάζεται να τροποποιήσετε τη συλλογή κατά την επανάληψη, πόσο σημαντική είναι η απόδοση και αν ο κώδικας τρέχει σε περιβάλλον πολλαπλών νημάτων. CopyOnWriteArrayList/CopyOnWriteArraySet είναι κατάλληλα όταν οι αναγνώσεις είναι πολύ περισσότερες από τις εγγραφές. Το κλείδωμα είναι κατάλληλο όταν οι αλλαγές συμβαίνουν σπανιότερα, αλλά μπορεί να είναι πιο συχνές από τη χρήση του CopyOnWrite. Η χρήση του Iterator.remove() ή η επανάληψη σε αντίγραφο είναι κατάλληλες όταν οι αλλαγές συμβαίνουν μόνο εντός μιας επανάληψης ή νήματος.