Swing-Threading und Event-Dispatch-Thread

Zurück 1 2 3 4 5 Page 5 Seite 5 von 5

Swing-Faden sicher aufbewahren

Der letzte Schritt beim Erstellen einer Swing-GUI besteht darin, sie zu starten. Die richtige Art, eine Swing-GUI heute zu starten, unterscheidet sich von dem ursprünglich von Sun vorgeschriebenen Ansatz. Hier noch einmal das Zitat aus der Sun-Dokumentation:

Sobald eine Swing-Komponente realisiert wurde, sollte der gesamte Code, der den Status dieser Komponente beeinflussen oder davon abhängen könnte, im Event-Dispatching-Thread ausgeführt werden.

Werfen Sie diese Anweisungen nun aus dem Fenster, denn als JSE 1.5 veröffentlicht wurde, haben sich alle Beispiele auf der Sun-Website geändert. Seit dieser Zeit ist es eine wenig bekannte Tatsache, dass Sie immer auf Swing-Komponenten im Event-Dispatch-Thread zugreifen sollen, um deren Thread-Sicherheit / Single-Thread-Zugriff zu gewährleisten. Der Grund für die Änderung ist einfach: Während Ihr Programm möglicherweise auf eine Swing-Komponente außerhalb des Event-Dispatch-Threads zugreift, bevor die Komponente realisiert wird, kann die Initialisierung der Swing-Benutzeroberfläche dazu führen, dass anschließend etwas auf dem Event-Dispatch-Thread ausgeführt wird Die Komponente / Benutzeroberfläche erwartet, dass alles im Event-Dispatch-Thread ausgeführt wird. Wenn GUI-Komponenten auf verschiedenen Threads ausgeführt werden, bricht das Single-Thread-Programmiermodell von Swing.

Das Programm in Listing 5 ist nicht ganz realistisch, aber es dient dazu, meinen Standpunkt zu verdeutlichen.

Listing 5. Zugriff auf den Swing-Komponentenstatus über mehrere Threads

import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BadSwingButton { public static void main(String args[]) { JFrame frame = new JFrame("Title"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton button = new JButton("Press Here"); ContainerListener container = new ContainerAdapter() { public void componentAdded(final ContainerEvent e) { SwingWorker worker = new SwingWorker() { protected String doInBackground() throws InterruptedException { Thread.sleep(250); return null; } protected void done() { System.out.println("On the event thread? : " + EventQueue.isDispatchThread()); JButton button = (JButton)e.getChild(); String label = button.getText(); button.setText(label + "0"); } }; worker.execute(); } }; frame.getContentPane().addContainerListener(container); frame.add(button, BorderLayout.CENTER); frame.setSize(200, 200); try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.println("I'm about to be realized: " + EventQueue.isDispatchThread()); frame.setVisible(true); } }

Beachten Sie, dass in der Ausgabe Code angezeigt wird, der auf dem Hauptthread ausgeführt wird, bevor die Benutzeroberfläche realisiert wird. Dies bedeutet, dass der Initialisierungscode auf einem Thread ausgeführt wird, während der andere UI-Code auf dem Event-Dispatch-Thread ausgeführt wird, wodurch das Single-Thread-Zugriffsmodell von Swing beschädigt wird:

> java BadSwingButton On the event thread? : true I'm about to be realized: false

Das Programm in Listing 5 aktualisiert die Beschriftung der Schaltfläche vom Container-Listener, wenn die Schaltfläche zum Container hinzugefügt wird. Um das Szenario realistischer zu gestalten, stellen Sie sich eine Benutzeroberfläche vor, die Beschriftungen darin "zählt" und die Anzahl als Text im Randtitel verwendet. Natürlich müsste der Titeltext des Rahmens im Event-Dispatch-Thread aktualisiert werden. Um die Sache einfach zu halten, aktualisiert das Programm nur die Beschriftung einer Schaltfläche. Obwohl dieses Programm in seiner Funktion nicht realistisch ist, zeigt es das Problem mit jedem Swing-Programm, das seit Beginn der Swing-Zeit geschrieben wurde. (Oder zumindest alle, die dem empfohlenen Threading-Modell folgten, das in den Javadocs und Online-Tutorials von Sun Microsystems und sogar in meinen eigenen frühen Ausgaben von Swing-Programmierbüchern zu finden ist.)

Swing Threading richtig gemacht

Der Weg, um Swing richtig einzufädeln, besteht darin, Suns ursprüngliches Diktum zu vergessen. Machen Sie sich keine Sorgen darüber, ob eine Komponente realisiert wird oder nicht. Versuchen Sie nicht festzustellen, ob es sicher ist, auf etwas außerhalb des Event-Dispatch-Threads zuzugreifen. Das ist es nie. Erstellen Sie stattdessen die gesamte Benutzeroberfläche im Event-Dispatch-Thread. Wenn Sie den gesamten Aufruf der Benutzeroberflächenerstellung innerhalb eines Aufrufs platzieren, werden EventQueue.invokeLater()alle Zugriffe während der Initialisierung garantiert im Event-Dispatch-Thread ausgeführt. So einfach ist das.

Listing 6. Alles an seinem Platz

import java.awt.*; import java.awt.event.*; import javax.swing.*; public class GoodSwingButton { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Title"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton button = new JButton("Press Here"); ContainerListener container = new ContainerAdapter() { public void componentAdded(final ContainerEvent e) { SwingWorker worker = new SwingWorker() { protected String doInBackground() throws InterruptedException { return null; } protected void done() { System.out.println("On the event thread? : " + EventQueue.isDispatchThread()); JButton button = (JButton)e.getChild(); String label = button.getText(); button.setText(label + "0"); } }; worker.execute(); } }; frame.getContentPane().addContainerListener(container); frame.add(button, BorderLayout.CENTER); frame.setSize(200, 200); System.out.println("I'm about to be realized: " + EventQueue.isDispatchThread()); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }

Führen Sie es jetzt aus, und das obige Programm zeigt an, dass sowohl die Initialisierung als auch der Containercode auf dem Event-Dispatch-Thread ausgeführt werden:

> java GoodSwingButton I'm about to be realized: true On the event thread? : true

Abschließend

Die zusätzliche Arbeit zum Erstellen Ihrer Benutzeroberfläche im Event-Dispatch-Thread scheint zunächst unnötig. Schließlich machen es seit jeher alle anders. Warum sich jetzt die Mühe machen, sich zu ändern? Die Sache ist, wir haben es immer falsch gemacht. Um sicherzustellen, dass auf Ihre Swing-Komponenten korrekt zugegriffen wird, sollten Sie immer die gesamte Benutzeroberfläche im Event-Dispatch-Thread erstellen, wie hier gezeigt:

Runnable runner = new Runnable() { public void run() { // ...create UI here... } } EventQueue.invokeLater(runner);

Das Verschieben Ihres Initialisierungscodes in den Event-Dispatch-Thread ist die einzige Möglichkeit, um sicherzustellen, dass Ihre Swing-GUIs threadsicher sind. Ja, es wird sich zunächst unangenehm anfühlen, aber Fortschritte tun dies normalerweise.

John Zukowski spielt seit mehr als 12 Jahren mit Java, nachdem er seine C- und X-Windows-Denkweise vor langer Zeit aufgegeben hat. Mit 10 Büchern zu Themen von Swing über Sammlungen bis hin zu Java SE 6 bietet John jetzt strategische Technologieberatung über sein Unternehmen JZ Ventures, Inc. an.

Erfahren Sie mehr über dieses Thema

  • Erfahren Sie mehr über die Swing-Programmierung und den Event-Dispatch-Thread von einem der Meister der Java-Desktop-Entwicklung: Chet Haase über die Maximierung von Swing und Java 2D (JavaWorld Java Technology Insider-Podcast, August 2007).
  • "Passen Sie SwingWorker an, um die Swing-GUIs zu verbessern" (Yexin Chen, JavaWorld, Juni 2003) befasst sich eingehender mit einigen der in diesem Artikel diskutierten Swing-Threading-Herausforderungen und erklärt, wie ein Customized SwingWorkerden Muskel bereitstellen kann, um sie zu umgehen .
  • "Java und Ereignisbehandlung" (Todd Sundsted, JavaWorld, August 1996) ist eine Einführung in die Ereignisbehandlung um AWT.
  • "Beschleunigen der Listener-Benachrichtigung" (Robert Hastings, JavaWorld, Februar 2000) führt die JavaBeans 1.0-Spezifikation für die Registrierung und Benachrichtigung von Ereignissen ein.
  • "Mit Threads eine starke Leistung erzielen, Teil 1" (Jeff Friesen, JavaWorld, Mai 2002) führt Java-Threads ein. In Teil 2 finden Sie eine Antwort auf die Frage: Warum brauchen wir eine Synchronisation?
  • "Ausführen von Aufgaben in Threads" ist ein JavaWorld-Auszug aus Java Concurrency in Practice (Brian Goetz et al., Addison Wesley Professional, Mai 2006), der die aufgabenbasierte Thread-Programmierung fördert und ein Ausführungsframework für die Aufgabenverwaltung einführt.
  • "Threads and Swing" (Hans Muller und Kathy Walrath, April 1998) ist eine der frühesten offiziellen Referenzen für Swing Threading. Es enthält die jetzt berühmte (und fehlerhafte) "Single-Thread-Regel".
  • Das Erstellen einer GUI mit JFC / Swing ist die umfassende Java-Tutorial-Seite für die Swing-GUI-Programmierung.
  • "Concurrency in Swing" ist ein Tutorial auf dem Swing Trail, das eine Einführung in die SwingWorkerKlasse enthält.
  • JSR 296: Swing Application Framework ist derzeit eine Spezifikation in Bearbeitung. Weitere Informationen zu diesem nächsten Schritt in der Entwicklung der Swing-GUI-Programmierung finden Sie unter "Verwenden des Swing Application Framework" (John O'Conner, Sun Developer Network, Juli 2007).
  • Die gesamte Java AWT-Referenz (John Zukowski, O'Reilly, März 1997) ist kostenlos im O'Reilly-Online-Katalog verfügbar.
  • Johns definitiver Leitfaden für Java Swing, dritte Ausgabe (Apress, Juni 2005) wurde vollständig für Java Standard Edition Version 5.0 aktualisiert. Lesen Sie hier auf JavaWorld ein Vorschau-Kapitel aus dem Buch !
  • Weitere Artikel über Swing-Programmierung und Java-Desktop-Entwicklung finden Sie im JavaWorld Swing / GUI-Forschungszentrum.
  • In den JavaWorld-Entwicklerforen finden Sie auch Diskussionen und Fragen und Antworten zur Swing- und Java-Desktop-Programmierung.

Diese Geschichte, "Swing-Threading und der Event-Dispatch-Thread", wurde ursprünglich von JavaWorld veröffentlicht.