Programmierung
Kopieren über Schreiben
Der Zugriff auf gemeinsam benutzte veränderliche Daten sollte in nebenläufigen Programmen synchronisiert werden, weil ein kritischer Wettlauf (race condition) zu Zugriffsfehlern und zu nicht deterministischem Verhalten führt (s.a. Item 66 in Effective Java von Joshua Bloch, 2. Auflage, 2008). In Java wird Synchronisation hergestellt, indem ein Thread (Ausführungsfaden) eine Sperre erwirbt (auch Monitor genannt), die durch das Schlüsselwort synchronized
mit einem Code-Abschnitt verknüpft ist.
Eine übermäßige Synchronisation sollte ebenso vermieden werden, denn sie kann zu verminderter Leistung, einem Deadlock, und ebenfalls zu nicht deterministischen Verhalten führen (Item 67 in Effective Java). Jeder Java-Programmierer lernt deshalb als Grundregel, innerhalb von synchronisierten Regionen so wenige Arbeitsschritte wie möglich durchzuführen.
Synchronisation ist ein wechselseitiger Ausschluss, was bedeutet, dass die Ausführung des gesperrten Abschnittes nicht von Daten abhängen sollte, die ebenfalls unter diesem Ausschluss stehen. Da der synchronisierte Code dies selbst garantieren muss, folgt daraus die Regel, wie von Bloch formuliert:
Um einen Bruch in Bezug auf Lebendigkeit und Sicherheit zu vermeiden, übergebe innerhalb von synchronisierten Methoden oder Blöcken niemals die Kontrolle an den Benutzer.
Bloch veranschaulicht dies mit dem Entwurfsmuster Observer (Beobachter) als Beispiel, einem der häufigst angewandten der Design Patterns, welche die sogenannte Gang of Four in ihrem berühmten 1995 erschienenen Buch beschrieben hat. Wenn der Aufruf der Beobachtermethode (Observer.call) synchronisiert wird, weil dem beobachtbaren Subjekt (Observable) gleichzeitig ein weiterer Beobachter (Observer) hinzugefügt oder einer entfernt werden kann, besteht die Gefahr, dass die Beobachtermethode eine problematische Situation herstellt, etwa eine Verklemmung (Deadlock), indem sie versucht, den vom Subjekt gehaltenen Monitor zu erwerben. Bloch zeigt die Lösung, weist aber auch auf eine noch einfachere hin – die Verwendung eines CopyOnWriteArrayList
, eine mit Java 1.5 im Paket java.util.concurrent
eingeführte Variante eines ArrayList
für spezielle Anwendungsfälle.
Den Unterschied von Ansicht oder Kopie von Listen haben wir bereits früher erörtert. Ein CopyOnWriteArrayList
stellt eine weitere Variante dar, weil hier bei einem Schreibzugriff eine Kopie des internen Feldes erstellt wird, um eine zur Zeit der Änderung in einem anderen Thread iterierte Ansicht nicht zu korrumpieren. Auf diese Weise wird eine Ausnahme über eine nebenläufige Änderung (ConcurrentModificationException
) vermieden, ohne dass Zugriffe auf die Liste synchronisiert werden müssen. Für einen Anwendungsfall wie beim Beobachtermuster ist dies nicht zu teuer, denn Änderungen der Liste der Beobachter werden weit weniger häufig als Iterationen über diese bei der Beobachterbenachrichtigung vorkommen, der Umfang der Liste ist typischerweise gering, und wenn Hinzufügung oder Entfernung eines Beobachters eine Benachrichtigungsiteration auslösen, ist garantiert, dass sie für alle Beobachter tatsächlich erfolgt.