![]() |
|
|
Listing 15.48 SwingNoSyncDemo.java import javax.swing.*; public class SwingNoSyncDemo { public static void main( String args[] ) { final DefaultListModel model = new DefaultListModel(); JList list = new JList( model ); JFrame frame = new JFrame(); frame.getContentPane().add( list ); frame.setSize( 200, 100 ); frame.setVisible( true ); new Thread(){ public void run() { setPriority( Thread.MIN_PRIORITY ); while ( true ) model.addElement( "Dumm gelaufen" ); } }.start(); new Thread(){ 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 occurred during event dispatching: java.lang.ArrayIndexOutOfBoundsException: 4145 >= 4145 at java.util.Vector.elementAt(Vector.java:417) at javax.swing.DefaultListModel.getElementAt(DefaultListModel.java:70) ... at java.awt.Component.dispatchEvent(Component.java:2499) at java.awt.EventQueue.dispatchEvent(EventQueue.java:319) at java.awt.EventDispatchThread.pumpOneEvent(EventDispatchThread.java:103) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93) at java.awt.EventDispatchThread.run(EventDispatchThread.java:84) Exception occurred during event dispatching: java.lang.ArrayIndexOutOfBoundsException: 4288 >= 4288 at java.util.Vector.elementAt(Vector.java:417) at javax.swing.DefaultListModel.getElementAt(DefaultListModel.java:70) ... Obwohl als unterliegende Datenstruktur der Vektor vorhanden ist, der, wie wir wissen, nur synchronisierte Methoden besitzt, ist er nicht 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 Modell informiert also das Darstellungsobjekt, 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 aber mindestens ein Wert. Daher folgt eine ArrayIndexOutOfBoundsException in der Methode elementAt() vom Vektor: java.lang.ArrayIndexOutOfBoundsException: 4145 >= 4145 at java.util.Vector.elementAt(Vector.java:417) Werfen wir einen Blick in die Implementierung, dann erkennen wir das Problem: public synchronized Object elementAt( int index ) { if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException( index + " >= " + elementCount); } ... } Die Visualisierung fragt mit dem Index 4145 im Vektor nach, doch der Vektor hat vom Lösch-Thread schon ein Element abgeben müssen. Daher ist die interne Größe elementCount auch kleiner als der Index. Lösung für SwingEinige der Methoden, die dennoch synchronisiert sind, tragen Listener ein, so etwa bei JComponent addPropertyChangeListener(), removePropertyChangeListener() und addVetoableChangeListener(), removeVetoableChangeListener(). Bei JCheckBoxMenuItem ist es dann die einsame Methode setState(boolean), die synchronisiert ist. Es findet sich intern mal hier mal da ein synchronisierter Block. Ansonsten ist jedoch nicht viel dabei, und wir müssen unsere Teile synchronisiert ausführen. Um Programmstücke konform ausführen zu lassen, definiert Swing einige Methoden und Klassen. Dazu gehören:
15.32.2 Swing-Elemente bedienen mit invokeLater() und invokeAndWait()
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| invokeLater() legt einen Thread in die Warteschlage und kehrt sofort zurück. Die Methode ist somit asynchron. Der Aufrufer weiß nicht, wann der Programmcode abgearbeitet wird. |
| invokeAndWait() legt ebenfalls den Thread in die Warteschlange, verharrt aber so lange in der Methode, bis der Programmcode in run() aufgerufen wurde. Die Methode ist also synchron. |
Mit diesen Methoden lassen sich jetzt alle Manipulationen an der Oberfläche durchführen.
Beispiel Ein Fortschrittsbalken JProgressBar mit dem Namen bar soll in einer Schleife einer Berechnung angepasst werden.
EventQueue.invokeLater( new Runnable() { public void run() { bar. setValue ( i ); } } ); |
Bei der Auswahl der beiden Funktionen haben wir uns für den Fortschrittsbalken für invokeLater() entschieden. Es macht in der Regel wenig Sinn, die Methode so lange stehen zu lassen, bis die Anzeige auch wirklich gezeichnet wurde.
Ein Problem ist leider für sehr viele Applikationen, dass das Objekt zur Manipulation immer irgendwie sichtbar sein muss. Hier soll bar einfach direkt für die innere Klasse sichtbar sein.
Die Funktionen invokeLater() und invokeAndWait() befinden sich nicht nur in der Klasse EventQueue, sondern sind noch einmal in der Klasse SwingUtilities untergebracht. Daher ist es gleichgültig, ob wir EventQueue.invokeXXX() oder SwingUtilities.invokeXXX() schreiben. SwingUtilities hat vielleicht den Vorteil, dass das Paket java.awt für die EventQueue nicht importiert werden muss, sonst gibt es aber keinen Unterschied.
Genehmigen wir uns abschließend noch einen kurzen Blick auf die Implementierung. Es lässt sich schon erahnen, dass invokeLater() einfacher ist:
public static void invokeLater( Runnable runnable ) { Toolkit.getEventQueue().postEvent( new InvocationEvent(Toolkit.getDefaultToolkit(), runnable)); }
Das Ereignis, welches in die Event-Queue kommt, ist vom Typ InvocationEvent und damit ein AWTEvent. Wir übergeben unser Runnable-Objekt, damit der AWT-Thread später die run()-Methode aufrufen kann.
Die Methode invokeAndWait() ist etwas komplizierter, und wir wollen von der Implementierung nur wenige Zeilen betrachten. Im Prinzip macht die Methode das Gleiche wie invokeLater(), sie muss ebenfalls das InvocationEvent in die Warteschlange legen. Doch hinzu kommt, dass invokeAndWait() auf das Ende des Threads warten muss:
InvocationEvent event = new InvocationEvent( Toolkit.getDefaultToolkit(), runnable, lock, true); synchronized (lock) { Toolkit.getEventQueue().postEvent(event); lock.wait(); }
Das konstruierte InvocationEvent bekommt wieder als Argument das runnable. Jetzt erhält es aber zusätzlich ein Lock-Objekt. Wenn der AWT-Thread durch die Ereignis-Warteschlange geht und das InvocationEvent sieht, führt er wieder die run()-Methode aus. Anschließend informiert er über notify() das wartende Objekt. Dann steigt invokeAndWait() aus dem sychronized-Block aus, und es geht weiter.
| << zurück |
Copyright © Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.