Swing ist nicht thread-sicher
Die Tatsache, dass das Swing-Toolkit nicht thread-sicher ist, erstaunt vielleicht auf den ersten Blick. Das AWT ist thread-sicher, da AWT auf Plattform-Peer-Elemente vertraut. In einer List-Box unter dem AWT ist es problemlos möglich, ein Element einzufügen und parallel zu löschen. Doch auf die Synchronisation bei Swing wurde aus zwei Gründen verzichtet:
- Untersuchungen mit anderen grafischen Bibliotheken haben ergeben, dass Operationen in Threads zu ärgerlichen Deadlock-Situationen führen können.
- Der Verzicht auf Synchronisation kann die Ausführungsgeschwindigkeit erhöhen.
Beispiel: Swing weiß mit konkurrierenden Zugriffen nicht allzu viel anzufangen
Listing 15.48 com/tutego/insel/ui/swing/SwingNoSyncDemo.java
package com.tutego.insel.ui.swing;
import javax.swing.*;
public class SwingNoSyncDemo
{
public static void main( String[] args )
{
final DefaultListModel model = new DefaultListModel();
JFrame frame = new JFrame();
frame.add( new JList( model ) );
frame.setSize( 200, 100 );
frame.setVisible( true );
new Thread() {
@Override public void run() {
setPriority( Thread.MIN_PRIORITY );
while ( true )
model.addElement( "Dumm gelaufen" );
}
}.start();
new Thread() {
@Override public void run() {
setPriority( Thread.MIN_PRIORITY );
while ( true )
model.removeElement( "Dumm gelaufen" );
}
}.start();
}
}
Werfen wir einen Blick auf die Ausgabe, die erscheint, wenn das Programm nur kurz läuft:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 14 >= 14 at java.util.Vector.elementAt(Vector.java:432) at javax.swing.DefaultListModel.getElementAt(DefaultListModel.java:70) ... at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:149) at java.awt.EventDispatchThread.run(EventDispatchThread.java:110) Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 0 at javax.swing.plaf.basic.BasicListUI.updateLayoutState(BasicListUI.java:1155) at javax.swing.plaf.basic.BasicListUI.maybeUpdateLayoutState(BasicListUI. java:1098) at javax.swing.plaf.basic.BasicListUI.paint(BasicListUI.java:234) at javax.swing.plaf.ComponentUI.update(ComponentUI.java:142) ...
Obwohl als unterliegende Datenstruktur der Vektor vorhanden ist, der, wie wir wissen, nur synchronisierte Methoden besitzt, ist nicht er direkt der Übeltäter. Es liegt an Swing, wie mit den Daten umgegangen wird. Wenn der erste Thread Daten in das Model einfügt, muss die Visualisierung aktualisiert werden. Als Datenstruktur nimmt das Standardmodel einen java.util.Vector, der die Daten aufnimmt. Das Model informiert also das Darstellungsobjekt darüber, dass es den Inhalt neu zeichnen muss. Merken wir uns die Stelle. Das Darstellungsobjekt wird sich nun vom Model die Daten besorgen. Bis dahin läuft alles ganz gut. Doch der zweite Thread löscht parallel die Daten aus dem Model. Springen wir jetzt zur Markierung zurück. Irgendwann passiert es, dass zwischen der Benachrichtigung der Darstellungskomponenten und dem wirklichen Zeichnen etwas gelöscht wird. Die Visualisierung weiß aber davon nichts und versucht, alle Werte zu zeichnen; es fehlt jedoch mindestens ein Wert. Daher folgt eine ArrayIndexOutOfBoundsException in der Methode elementAt() vom Vektor. Die Visualisierung fragt mit einem Index im Vektor nach, doch der Vektor hat vom Lösch-Thread schon ein Element abgeben müssen. Daher ist die interne Größe des Vektors kleiner als der von Swing erfragte Index.