Size() und isEmpty() in bei Concurrent Containern

#1
concurrentContainer.remove( object );
if( concurrentContainer.isEmpty() && concurrentContainer.size() == 0 ) {
    doStuff();
}

Sagen wir ein anderer Thread iteriert gerade über concurrentContainer, kann ich mich immer auf die Ausführung des obigen Codes verlassen? Auf was bezieht sich isEmpty() bzw. size(), auf die zukünftige oder aktuelle Größe?

#2

Auf die aktuelle. Du musst es syncen…

Edit: und size()… nach isEmpty() ist nicht nötig.

1 Like
#3

Danke.
Darf ich fragen woher du das weißt? Selber schon mal damit beschäftigt?

Wie du dir vielleicht selber denken kannst war das nur Pseudocode um ohne weitere Worte Beide Möglichkeiten der Abfrage anzusprechen. Soviel dazu :smiley:

#5

is ‘Concurrent Container’ ein Fachbegriff, bei dem man sofort an eine bestimmte Klasse/ Gruppe denken soll?

selbst falls etwa
https://docs.oracle.com/javase/tutorial/essential/concurrency/collections.html
gemeint ist gibt es da doch sicher noch Feinheiten, unterschiedliche Klassen mit eigenen Verhalten

ganz allgemein gesprochen, ohne mehr Gewähr als sicher auch selber zu denken:
isEmpty() vs. size() == 0 ist gewiss immer äquivalent,
zwei Aufrufe davon, egal welche beiden, hintereinander können aber durchaus unterschiedliches liefern, wenn eben von Threads unterbrochen

eine Unterscheidung zukünfig vs. aktuell klingt unpassend,
bei einer Abfrage ist der aktuelle Stand dran, Änderungen sind entweder unbekannt, nicht für die Zukunft zu erahnen, oder bereits vollständig umgesetzt,
warum gerade ‘Iterierung anderer Thread’ genannt, ist da Änderung zu erwarten?

vorstellbar ist ein Konzept wie hier
https://www.developer.com/java/data/an-introduction-to-concurrent-collection-apis-in-java.html
nebenbei für CopyOnWriterArrayList gelesen, dass bei einer länger dauernden Abfragen wie Iterator ein Stand zur Abfrage genutzt und länger durchlaufen wird, auch wenn zwischenzeitlich die Collection geändert wird

#6

Was bitte ist concurrentContainer? Eine WWW-Suche verweist immer auf concurrentCollections, aber ich denke mal, das ist nicht gemeint. Bei Containern denke ich immer an GUIs und da gibt es kein isEmpty() oder size(), sondern allenfalls ein getComponentCount(), welches letzlich zu einem components.size() (alles unsynchronisiert) delegiert und sich auf die aktuelle Größe bezieht. Die API scheint mir sehr speziell.

#7

Es waren natürlich Collections gemeint aus der Java SE Library und egal welche, denn in keiner der Docs wird darauf eingegangen. Ich könnte mir auch vorstellen dass es da eine übergreifende Regel im Bereich Concurrency geben muss.

Für mich ist ein Container von lateinisch für “zusammenhalten” oder “enthalten” das gleiche, und im normalen Leben meint man damit große Behältnisse zum Lagern und Transport von Objekten. Verzeihung für die Verwirrung, an Docker und GUIs hatte ich nicht gedacht, aber ich kann die Eingangsfrage nicht editieren.

Theoretisch ist isEmpty() zwar äquivalent zu size() == 0, aber nicht unbedingt in einem Mulithreading Kontext. Da könnte size() sich auf die aktuelle Größe beziehen würde man jetzt anfangen darüber zu iterieren und isEmpty() auf die zukünftige Größe - zum Beispiel, ich weiss es nicht.

Eben genau das war die Frage. Wenn ich remove(object) ausführe, erwarte ich dass size() sinkt. Wenn aber remove() zu einem späteren Zeitpunkt ausgeführt wird, weil gerade noch andere typische Operationen auf der Concurrent Collection ausgeführt werden, dann ist die Frage berechtigt ob sich size() auf die aktuelle oder zukünftige Größe bezieht.
Wie gesagt, die Docs schweigen sich dazu aus.

Aber CB hat mir die Frage bereits beantwortet und auch quellenmäßig untermauert, ist immerhin kein 0815 Programmiererwissen. Das nehme ich mal so mit, danke dafür nochmals, sollte sich das als falsch herausstellen komme ich wieder :smiling_imp: :smiley:

#8

Dem ist nicht so:

Beware that, unlike in most collections, the size method is not a constant-time operation. Because of the asynchronous nature of these sets, determining the current number of elements requires a traversal of the elements, and so may report inaccurate results if this collection is modified during traversal.

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentSkipListSet.html

Deshalb sollte man sich immer auf die konkrete Klasse beziehen beim coden und fragen wenn es um Concurrency geht.

#9

Eigentlich ist das ja schon durch, aber nochmal meine 2 cents:

Ich nehme an, dass mit “concurrent container” entweder…

  • (unwahrscheinlich: ) irgendeine Collection gemeint war, die von mehreren Threads verwendet wird, oder
  • irgendeine der Collections aus dem java.util.concurrent package

Ersteres ist unwahrschenlich. Das kracht praktisch zwangsläufig früher oder später irgendwo. Aber auch die Container/Collections aus java.util.concurrent sind nur “thread safe” in dem Sinne, dass es nicht “kracht”. Ob der Zustand, den man sieht, in irgendeinem Sinne “konsistent” ist (speziell in dem Sinne, ob der beobachtete Zustand den (unfundierten und aus Wunschdenken resultierenden :stuck_out_tongue_winking_eye: ) Erwartungen des Programmierers entspricht), ist eine komplett andere Frage.

Darüber hinaus: Die Unterscheidung zwischen “zukünftige und aktuelle Größe” macht tatsächlich nicht viel Sinn. Jede Unterhaltung darüber würde es erfordern, dass man weeeeit ausholt und über das Java Memory Model, “Happens Before” und das Konzept von Zeit an sich redet.

Die Quintessenz ist:

In der skizzierten Abfrage könnte es sein, dass isEmpty() ein true liefert, aber size() == 0 dann false ist, weil zwischendrin ein anderer Thread etwas in den Container gepackt hat. (Durch reines Iterieren, wie in der Frage geschrieben, ändert sich da natürlich nichts, aber ich nehme an, dass es um den Fall ging, wo ein anderer Thread Elemente hinzufügt oder entfernt).

Und das kann auch sein, obwohl die Standard-Implementierung von isEmpty in AbstractCollection so aussieht:

/**
 * {@inheritDoc}
 *
 * <p>This implementation returns <tt>size() == 0</tt>.
 */
public boolean isEmpty() {
    return size() == 0;
}

(Gerade bei … “ausgefeilteren” Collections, wie ConcurrentLinkedQueue oder so, kann die Implementierung anders sein, aber das ist hier nicht soooo relevant…)

Ohne irgendeine Form von Synchronisation sind die beiden Aufrufe in der if-Abfrage komplett getrennte Dinge, zwischen denen (theoretisch) einige Minuten liegen können, in denen ein anderer Thread die Collection befüllt, drüberiteriert, nochmal leert, wieder füllt,… und damit am Ende “irgendein” Ergebnis bei der if-Abfrage rauskommt.