Collections CopyOnWriteArrayList - wie genau verwenden

redbomber

Bekanntes Mitglied
Hi zusammen,
ich verwende aktuell eine CopyOnWriteArrayList, zu der ich aber noch einige Fragen habe.
Ich habe verschiedene Beschriebungen zu diesem speziellen Datentyp gelesen.

Diese CopyOnWriteArrayList bietet sich bei mir an, da ich vorallem von dieser Liste lese und nur selten hinzufüge/entferne.

Allen Beschreibungen konnte ich leider keine einfach Implementierung entnehmen bzw. und wenn, dann waren diese sehr unterschiedlich.
In einem Fall wurde diese Liste bei hinzufügen/entfernen mit einem Lock versehen, bei einem anderem Beispiel nicht. Was ist nun richtig?

Hier kommt mal ein einfacher Beispiel Code:
Java:
import java.util.concurrent.CopyOnWriteArrayList;

public class MyContainer {

   private CopyOnWriteArrayList<MyObjects> myObjectList;

   public MyContainer() {
      myObjectList = new CopyOnWriteArrayList<MyObjects>();
   }

   /**
    * remove all operation.
    */
   public void removeAll() {
      MyObjects myObject = null;
      for (int lIndex = myObjectList.size() - 1; lIndex >= 0; lIndex--) {
         myObject = myObjectList.get(lIndex);
         myObject.shutMeDown();
         myObjectList.remove(lIndex);
      }
   }

   public void removeOneObject(MyObjects myObjectToDelete) {
      if (myObjectToDelete == null) {
         return;
      }
      MyObjects myObject;
      for (int lIndex = myObjectList.size() - 1; lIndex >= 0; lIndex--) {
         myObject = myObjectList.get(lIndex);
         if (myObject.equals(myObjectToDelete)) {
            myObject.shutMeDown();
            myObjectList.remove(lIndex);
            break;
         }
      }
   }

   /**
    * add operation.
    */
   private void addNewMyObject() {
      myObjectList.add(new MyObjects());
   }

   /**
    * return one operation.
    * @return
    */
   private MyObjects getFreeObject() {
      for (MyObjects myObject : myObjectList) {
         if (myObject.isFree()) {
            return myObject;
         }
      }
      return null;
   }
}

class MyObjects {
   private boolean free = true;

   public void shutMeDown() {
      // shut me down
   }

   public boolean isFree() {
      return free;
   }

}

Mein Container unterstützt 4 Operationen auf der CopyOnWriteArrayList.
  1. Lösche alle Objekte
  2. Lösche ein Objekt
  3. Füge ein Objekt hinzu
  4. Hole ein Objekt

Meine Frage ist nun:
Die Methoden, bei denen ich Modifikationen auf der CopyOnWriteArrayList durchführe, kann ich diese so implementieren wie in dem Beispiel, oder muss ich das anders implementieren?
 

KrokoDiehl

Top Contributor
Hallo. Zunächst einmal verstehe ich diese spezielle ArrayList so, dass sie hauptsächlich auf "sichere" Iteratoren aus ist. Das ein Iterator arbeitet immer nur auf einem Schnappschuss der Liste und Änderungen während der Iteration bekommt der Iterator nicht mit, gibt dafür auch keine Ausnahme beim Bearbeiten der Liste.
Insofern brauchst du zumindest bei deinen add-Methoden keine eigenes Locking.

Deine
Code:
removeOneObject()
finde ich etwas untypisch. Ich verstehe dass deine Objekte erstmal ein
Code:
shutMeDown()
brauchen, aber ginge es nicht auch so:
Java:
public void removeOneObject(MyObjects myObjectToDelete) {
    if (myObjectToDelete != null) {
        int index = myObjectList.indexOf(myObjectToDelete);
        if (index >= 0) {
                myObjectToDelete.shutMeDown();
                myObjectList.remove(myObjectToDelete); //hier kein index, denn vllt ist bereits ein anderes Element an dieser Stelle
        } //if Objekt ist in Liste
    }

Nun bleibt generell die Frage mit der Sicherheit, vor allem beim
Code:
removeAll()
, denn während du alle Objekte shutMeDown()st könnte die Liste ja verändert werden. Hier ist die Frage ob das im Sinne der Anwendung ist, wenn
Code:
removeAll()
heißt "lösche alle die sich zum Zeitpunkt des Aufrufs in der Liste befinden".
In dem Fall kannst du "einfach" mit einem Snapshot-Iterator über die Liste gehen:
Java:
public void removeAll() {
    for (MyObjects obj : myObjectList) {
        obj.shutMeDown();
        myObjectList.remove(obj); //hier keine CurrentModificationException!
    }
}
 

redbomber

Bekanntes Mitglied
Hey,
vielen Dank für die Antwort.

Also zu
Java:
removeOneObject()
:
Das mit dem Index verstehe ich. Hatte dies aus performance gründen so gemacht, da ich ja den Index schon ermittelt habe und somit ja gleich das Objekt an dieser Stelle löschen könnte.
Verstehe aber, dass in diesem Fall das Objekt an dieser Stelle sich bereits geändert haben könnte.
Also muss ich deine Variante verwenden.

Zu
Java:
removeAll()
:
Wenn ich das jetzt richtig verstehe, verwendet diese Variante der Iteration einen Snapshot der Liste?
Java:
for (MyObjects obj : myObjectList) {
...
}
Falls ja dann wäre das doch für meine Methode
Java:
getFreeObject()
falsch? Hier möchte ich ja nur lesen (was sehr häufig passiert). Wie müsste ich dann hier über die Liste iterieren?


Bei den Operationen CopyOnWriteArrayList.remove(...) und CopyOnWriteArrayList.add(...) kann keine CurrentModificationException auftreten, diese Arten der Modifikation sind also sicher!?
 

KrokoDiehl

Top Contributor
Vorweg: Ich habe mit dieser Art von Liste noch nicht selbst gearbeitet sondern berufe mich auf die API-Dokumentation (CopyOnWriteArrayList (Java Platform SE 7 )).

Deine Verwendung von
Code:
getFreeObject()
halte ich nicht für falsch. Die Suche nach dem freeObject läuft zwar über eine Momentaufnahme der Liste, aber genau dafür ist diese Listen-Implementierung ja "gut": An der einen Stelle kannst du gemütlich drüber iterieren und beeinflusst damit nicht andere Threads, die ggfs. etwas einfügen und dann keine Ausnahme bekommen. Das beantworten auch deine letzte Frage: Deartige Ausnahmen treten nicht auf. Dafür ist es natürlich möglich, dass Thread A iteriert und währendessen Thread B etwas einfügt. A bekommt dieses neue Objekt dann aber nicht mit.

Wenn du natürlich zu jedem Zeitpunkt überall die gleiche Liste haben willst, musst du mit vollem Locking etc. arbeiten. Aber laut deiner Aussage wirst du selten Einfügen/Löschen und viel Iterieren. Klingt also durchaus nach einem Einsatzgebiet für die CopyOnWriteArrayList.

In meiner Variante von
Code:
removeOneObject()
ist das mit dem Index übrigens unnötig. Ein
Code:
contains()
wäre da verständlicher denn es geht eigentlich nur darum, zu erfahren, ob das Objekt in der Liste ist.
 

redbomber

Bekanntes Mitglied
Hey,
nochmals vielen Dank.
Habe das soweit getestet, aberleider ist mir immer noch einiges unklar.

Zusammengefasst gibt es ja zwei unterschiedliche Gruppen von Operation auf der Liste:
1. Löschen/Hinzufügen
2. lesen

Wie in der doku beschrieben, ist die CopyOnWriteArraylList genau dann gut, wenn oft gelesen, aber nur selten hinzugefügt/gelösct wird.

Aktuell habe ich diese Gruppen wie folgt implementiert:
Löschen:
Java:
public void removeAll() {
    for (MyObjects obj : myObjectList) {
        obj.shutMeDown();
        myObjectList.remove(obj); //hier keine CurrentModificationException!
    }
}

Lesen:
Java:
private MyObjects getFreeObject() {
      for (MyObjects myObject : myObjectList) {
         if (myObject.isFree()) {
            return myObject;
         }
      }
      return null;
   }

Wie man sieht verwende ich in beiden Fällen den Iterator.

Jetzt entnehme ich aber der Doku (siehe z.B. hier: AngelikaLanger.com - CopyOnWriteArrayList - Angelika Langer Training/Consulting) folgendes:

1. immer wenn ich den Iterator verwende, wird ein Snapshot der Liste angelegt.
Frage: Dieses Snapshot anlegen ist doch das teure und sollte daher nur selten gemacht werden?
Den iterator bei meiner Lese-Operation zu verwenden ist dann doch falsch?

2. Wird mittels Iterator über die Liste iteriert, kann keine Modifikation an der Liste erfolgen (da ja auf dem Snapshot, altem Stand der Liste, gearbeitet wird). Aber in meinem Code-Beispiel
Java:
removeAll()
mache ich doch genau das? Bedeutet dies dass diese Änderung dann nicht durchgeführt wird?

Sorry, aber alle Quellen die ich finde bringen mich leider nicht weiter :(
 

KrokoDiehl

Top Contributor
Gruezi.
Du liegst etwas daneben. Ich habe nicht alles gelesen was du gelinkt hast und picke mir nur einen Satz raus, der meine Aussage unterstützt (...wie das moderne Medien so machen ;-) ):
Im Gegenzug sind die modifizierenden Methoden relativ teuer, weil sie die Modifikation auf einer Kopie des unterliegenden Arrays ausführen und anschließend das alte gegen das neue modifizierte Array austauschen.
Das Teure sind also die
Code:
add()
- und
Code:
remove()
-Methoden, denn hier wird eine Kopie der Liste erzeugt.

Vielleicht hilft ein Code-Beispiel weiter um es zu verstehen:
Java:
public final class CopyOnWriteTest {

    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        list.add("Eins");
        list.add("Zwei");
        list.add("Drei");
        list.add("Vier");
        list.add("Fünf");
        
        // "Snapshot"-Iterator 1
        Iterator<String> iter1 = list.iterator();
        
        list.add("Sechs");
        list.add("Sieben");
        
        // "Snapshot"-Iterator 2
        Iterator<String> iter2 = list.iterator();
        
        int count1 = 0;
        while (iter1.hasNext()) {
            iter1.next();
            count1++;
        }
        
        int count2 = 0;
        while (iter2.hasNext()) {
            iter2.next();
            count2++;
        }
        
        System.out.println("Snapshot 1: "+count1); // count1=5
        System.out.println("Snapshot 2: "+count2); // count2=7
    }
}
Obwohl beide Iteratoren auf der gleiche Liste arbeiten, haben sie eine unterschiedliche Grundmenge: Iterator 1 wurde früher "erzeugt" und hat daher nur die Elemente "Eins" bis "Fünf".
Gleicher Code mit einer ArrayList liefert übrigens eine ConcurrentModificationException.

Du kannst die ja auch mal die Implementierung der CopyOnWrite-Liste anschauen, hier sieht man auch das bei add() usw. das Array kopiert wird, bei
Code:
iterator()
wird nur ein neuer Iterator erzeugt.
 

xehpuk

Top Contributor
1. immer wenn ich den Iterator verwende, wird ein Snapshot der Liste angelegt.
Frage: Dieses Snapshot anlegen ist doch das teure und sollte daher nur selten gemacht werden?
Den iterator bei meiner Lese-Operation zu verwenden ist dann doch falsch?
Es wird ein Iterator erzeugt, der direkt auf dem zugrunde liegenden Array arbeitet. Es wird also keine Kopie erzeugt. Die modifizierenden Operationen rühren das alte Array nicht an, sondern kopieren dessen Inhalt über
Code:
Arrays.copyOf()
in ein neues Array. Auf diesem wird dann die Änderung vorgenommen und dann wird das alte Array über normale Zuweisung durch dieses neue ersetzt. Daher sehen davor erzeugte Iteratoren keine Änderungen. Deswegen die Bezeichnung Snapshot.

2. Wird mittels Iterator über die Liste iteriert, kann keine Modifikation an der Liste erfolgen (da ja auf dem Snapshot, altem Stand der Liste, gearbeitet wird). Aber in meinem Code-Beispiel
Java:
removeAll()
mache ich doch genau das? Bedeutet dies dass diese Änderung dann nicht durchgeführt wird?
Achtung: Du darfst die modifizierenden Iterator-Methoden nicht aufrufen. In der foreach geht das sowieso nicht, weil dort der Iterator nicht zugänglich ist. Die modifizierenden List-Methoden darfst du natürlich aufrufen.
 
Zuletzt bearbeitet:

redbomber

Bekanntes Mitglied
Hey, vielen Dank euch beiden!

Mit Sicherheit steht in den Quellen in denen ich nachgelesen habe auch alles drinnen, aber ich habs einfach nicht ganz verstanden gehabt.
Aber jetzt hab ichs dank eurer Erklärung begriffen, vielen Dank!

Du kannst die ja auch mal die Implementierung der CopyOnWrite-Liste anschauen, hier sieht man auch das bei add() usw. das Array kopiert wird, bei
Code:
iterator()
wird nur ein neuer Iterator erzeugt.
Wenn ich ehrlich bin hab ich den source code schon angeschaut, aber irgendwie war ich da schon verwirrt und da hat mir das dann irgendwie auch nicht weitergeholfen.

Auf jeden Fall klappt es jetzt und ich denke ich hab jetzt alles richtig!
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
jhCDtGVjcZGcfzug Klassen Was genau passiert hier? Kann mir das jemand bitte Zeile für Zeile erklären? Allgemeine Java-Themen 1
berserkerdq2 Threads, wie genau läuft das in Java ab? (Ich kann Threads erstellen und nutzen, nur das Verständnis) Allgemeine Java-Themen 6
W Was genau sind IOTools? Kann ich stattdessen nicht die Scanner Klasse verwenden? Allgemeine Java-Themen 3
M Klasse in separaten Thread ausführen.Wie genau? Allgemeine Java-Themen 2
Q javax.crypto.BadPaddingException - was ist das genau? Allgemeine Java-Themen 9
L Wie genau soll ein Klassendiagramm sein Allgemeine Java-Themen 4
C Rechnung genau 8 mal ausführen Allgemeine Java-Themen 5
B notify() - was passiert danach genau? Allgemeine Java-Themen 8
S Was genau heisst ServletContext? Allgemeine Java-Themen 3
H Layout auslagern, aber wie genau??? Allgemeine Java-Themen 11
A Was ist der genau Sinn eines Interface? Allgemeine Java-Themen 13
H Was ist der genau Sinn von tString()? Allgemeine Java-Themen 3
B Verlinken, was genau? Allgemeine Java-Themen 5
W "int" einlesen - wie genau funktioniert das ? Allgemeine Java-Themen 2
O regulärer Ausdruck zum durchsuchen eines Strings verwenden Allgemeine Java-Themen 2
J Welchen Packager soll ich verwenden. Allgemeine Java-Themen 7
H lokale Variable bei Optional verwenden Allgemeine Java-Themen 11
M Exceptions - wann / wie verwenden? Allgemeine Java-Themen 4
S Array dynamisieren oder ArrayList verwenden? Allgemeine Java-Themen 17
bueseb84 Git : Mehrere Server verwenden Allgemeine Java-Themen 3
D Klassen JLabels in anderen Klassen verwenden. Allgemeine Java-Themen 7
D API Keys mehrmals verwenden Allgemeine Java-Themen 6
K For-Schleife <> oder != Operator verwenden? Allgemeine Java-Themen 2
J Eigene Api erstellen und dann auch verwenden - Ordnerstruktur Allgemeine Java-Themen 1
J PDFBox kommerziell verwenden Allgemeine Java-Themen 15
S Variablen split-Funkton mit zwei Variabeln verwenden? Allgemeine Java-Themen 4
K jar-Datei lässt sich unter Windows nicht verwenden Allgemeine Java-Themen 3
M Java Mail verwenden Allgemeine Java-Themen 20
Neumi5694 Interface Generics für Enum-Filterung verwenden Allgemeine Java-Themen 5
J Interface Wofür Interfaces in Java verwenden? Allgemeine Java-Themen 3
Neumi5694 Operatoren regEx für das Erstellen eines Strings verwenden Allgemeine Java-Themen 3
O 3D-Grafik java3D in eclipse verwenden Allgemeine Java-Themen 0
J Allgemein gültige Klasse/Methode mehrfach verwenden Allgemeine Java-Themen 11
R Threads ReentrantLock mehrfach verwenden Allgemeine Java-Themen 3
J Interface Interface für Framework verwenden Allgemeine Java-Themen 4
G Services verwenden sich gegenseitig Allgemeine Java-Themen 4
R Dateimanager verwenden Allgemeine Java-Themen 2
P Variablen Variable von void im ActionListener verwenden Allgemeine Java-Themen 3
N VB Code in Java verwenden Allgemeine Java-Themen 5
P java.nio.file unter Java 6 verwenden Allgemeine Java-Themen 4
A Klassen String Array in anderer Klasse verwenden Allgemeine Java-Themen 11
P JDK installieren Mac OS X - JDK7 in Eclipse Juno verwenden Allgemeine Java-Themen 3
L Eine jar verwenden, die intern auf eine andere jar zugreift Allgemeine Java-Themen 7
S Aus Programm Klasse erstellen und verwenden Allgemeine Java-Themen 10
M Lock Datei intelligent verwenden Allgemeine Java-Themen 4
J Eure Meinung: Threads verwenden, oder nicht? Allgemeine Java-Themen 6
X Threads Thread mehrmals verwenden Allgemeine Java-Themen 4
G JGoodies Binding oder EventBus verwenden? Allgemeine Java-Themen 11
E Kann nicht gesamten HeapSpace verwenden Allgemeine Java-Themen 5
A Eigenen OutputStream schreiben und verwenden Allgemeine Java-Themen 9
M Poi-Excel: vorhandene Formatierung verwenden Allgemeine Java-Themen 2
B boolean return typ verwenden? Allgemeine Java-Themen 5
R ResourceBundle in Servlets verwenden Allgemeine Java-Themen 13
A Welche Exception verwenden? Allgemeine Java-Themen 2
R Java function die Strings escaped, sodass ich sie in Javascript verwenden kann? Allgemeine Java-Themen 4
MQue Vector<..> nicht verwenden Allgemeine Java-Themen 4
T Pfad aus Dateilesen -> wie diesen Pfad verwenden! Allgemeine Java-Themen 13
R in einem neuen Runnable() eine lokale Varibale verwenden Allgemeine Java-Themen 2
G dll verwenden Allgemeine Java-Themen 2
A Bestimmte JVM verwenden Allgemeine Java-Themen 13
R array.length in For-Schleife verwenden? Allgemeine Java-Themen 22
F Generics: spricht etwas dagegen raw types zu verwenden? Allgemeine Java-Themen 31
B Passwort verwenden ohne dass es im Quelltext steht Allgemeine Java-Themen 24
D Lostus Notes Adressbuch in eine Java-Webanwendung verwenden Allgemeine Java-Themen 4
G JasperReports: Verwenden von vorkompilierten Reports Allgemeine Java-Themen 2
ToNyXXL Als Mauszeiger eigenes Bild verwenden! Allgemeine Java-Themen 3
A Windows Bitmap-Handle in Java verwenden Allgemeine Java-Themen 2
G Ab wann Datenbank verwenden Allgemeine Java-Themen 15
D .dlls mit Java verwenden Allgemeine Java-Themen 2
M JNI mit vorhandenen libs oder dlls verwenden Allgemeine Java-Themen 2
N Klasse die in C geschrieben wurde in Java verwenden? Allgemeine Java-Themen 20
H Tastatur eingaben verwenden Allgemeine Java-Themen 30
E Statt HashSet die TreeSet verwenden Allgemeine Java-Themen 4
G eigene klassen die ein jar verwenden als neues jar erstellen Allgemeine Java-Themen 4
M java klassen beerben u. den gleichen namen verwenden?(Naming Allgemeine Java-Themen 6
S Änderungen im Source-Code direkt verwenden können? Allgemeine Java-Themen 3
R Sofort "eth0" verwenden Allgemeine Java-Themen 2
G Photoshop Plugins in Java verwenden. Allgemeine Java-Themen 3
P globale Varibalen verwenden? Allgemeine Java-Themen 13
G JRE 1.5.0-rc nicht als Standard-JRE verwenden Allgemeine Java-Themen 6
P Welche Collection verwenden? Allgemeine Java-Themen 4
S Comparator verwenden? Allgemeine Java-Themen 2
J System Tray verwenden Allgemeine Java-Themen 7

Ähnliche Java Themen

Neue Themen


Oben