- In der Vergangenheit erreichte die CPU-Auslastung meines Systems 3.200 %, sodass alle 32 Kerne vollständig ausgelastet waren
- Es lief die Java-17-Runtime, und nachdem ich in einem Thread-Dump die CPU-Zeit geprüft und nach CPU-Zeit sortiert hatte, fand ich viele ähnliche Threads
- Analyse des problematischen Codes
- Über den Stack-Trace wurde Zeile 29 der Klasse
BusinessLogic identifiziert
- Der betreffende Code iterierte über die Liste
unrelatedObjects und fügte dabei den Wert von relatedObject in treeMap ein
- Das war ineffizienter Code, da innerhalb der Schleife
unrelatedObject nicht verwendet wurde
Code-Änderung und Test
- Die unnötige Schleife wurde entfernt und durch die eine Zeile
treeMap.put(relatedObject.a(), relatedObject.b()); ersetzt
- Vor und nach der Änderung wurden Unit-Tests ausgeführt, das Problem ließ sich jedoch nicht reproduzieren
- Selbst wenn
treeMap und unrelatedObjects jeweils mehr als 1.000.000 Elemente enthielten, trat das Problem nicht auf
Ursache des Problems gefunden
- Auf
treeMap wurde gleichzeitig von mehreren Threads zugegriffen, ohne Synchronisierung
- Das Problem entstand also dadurch, dass mehrere Threads
TreeMap gleichzeitig veränderten
Reproduktion des Problems durch Experimente
- Es wurde ein Experiment durchgeführt, bei dem mehrere Threads eine gemeinsam genutzte
TreeMap zufällig aktualisierten
- Mit einem
try-catch-Block wurde festgelegt, NullPointerException zu ignorieren
- Das Experiment zeigte, dass die CPU-Auslastung auf bis zu 500 % ansteigen konnte
Fazit
- Gleichzeitige Änderungen an einer nicht synchronisierten
TreeMap können schwerwiegende Performance-Probleme verursachen
- Um solche Probleme zu vermeiden, wird empfohlen,
TreeMap zu synchronisieren oder eine threadsichere Collection wie ConcurrentMap zu verwenden
1 Kommentare
Hacker-News-Kommentare
Ich dachte bisher, Race Conditions würden Datenkorruption oder Deadlocks verursachen, hatte aber nicht bedacht, dass sie auch Performance-Probleme auslösen können. Daten können so beschädigt werden, dass sie Endlosschleifen erzeugen
Collections.synchronizedMapzu umhüllen oder aufConcurrentHashMapumzusteigen und bei Bedarf zu sortierenIn Code mit mehreren laufenden Threads ist die einzige sichere Strategie, alle Objekte unveränderlich zu machen und die Objekte, die nicht unveränderlich sein können, auf kleine, in sich geschlossene und streng kontrollierte Bereiche zu beschränken
Die Bemerkung „man konnte sich per ssh fast nicht einloggen“ erinnerte mich an meine Zeit im Graduiertenstudium mit einer Sun UltraSparc 170
Der Code lässt sich einfach auf Folgendes reduzieren
treeMap.putnur aus, wenn <i>unrelatedObjects</i> nicht leer war. Das könnte bereits ein Bug seinEine weitere Möglichkeit, eine Endlosschleife zu erzeugen, ist die Verwendung einer <i>Comparator</i>- oder <i>Comparable</i>-Implementierung, die keine konsistente totale Ordnung implementiert
Man könnte erwägen, Zyklen mit einem hochzählenden Counter zu erkennen und eine Exception zu werfen, wenn die Baumtiefe oder die Größe der Collection überschritten wird
In Java erzeugen konkurrierende Operationen auf nicht thread-safe Objekten die interessantesten Bugs
Es gibt die Frage, ob eine ungeschützte TreeMap 3.200 % Auslastung verursachen kann
Der Autor hat eine Art Poison Pill entdeckt. Das ist in Event-Sourcing-Systemen verbreiteter: eine Nachricht, die alles umbringt, womit sie in Kontakt kommt
Exceptions in Threads sind ein absolutes Problem
select()und Threads, die mit Exceptions um sich werfen