Neues Kapitel: Threads, Prozesse und GUIs Verteilte Systeme Hochschule Regensburg Vorlesung 3, 11.04.2012 Universitätsstraße 31, 93053 Regensburg Prof. Dr. Jan Dünnweber Die Kommunikation (s. vorherige Vorlesung) findet zwischen Prozessen (Programmen in Ausführung) statt Prozess als Konzept stammt aus dem Betriebssysteme-Bereich Inhalt des Kapitels: ◮ ◮ ◮ Das Konzept von Thread vs. Prozess Aufbau von Clients und Servern mit Threads Implementierung von GUIs mit Swing und Servlets Prof. Dr. Jan Dünnweber, Folie 2 von 27 Prozesse und Threads Verteilte Systeme Threads in der nicht-verteilten Welt Prozess: ein in Ausführung befindliches Programm, mit seinen Daten, Dateien, Registern, etc. Auf konventionellen Rechnern (z.B. PCs) arbeiten Prozesse i. d. R. nebenläufig zueinander auf einer CPU, d. h. sie teilen den Prozessor unter sich Das Betriebssystem sorgt für die Nebenläufigkeits-Transparenz: der Benutzer weiss nichts von den “fremden” Prozessen Das Umschalten des Prozessors zwischen den Prozessen ist aufwendig: der alte Adressraum muß gerettet und der neue geladen werden – sog. Kontextwechsel Threads sind Prozessen ähnlich, jedoch können mehrere Threads unter sich einen Adressraum teilen: Viele konventionelle Anwendungen laufen schneller mit mehreren Threads, z. B. ein Thread ist verantwortlich für Berechnungen und einer für die GUI-Interaktionen Derartige funktionelle Strukturierung ist häufig auch softwaretechnisch sinnvoll (vgl. 3-tier architecture und andere Schichtenmodelle) Mehrere Threads auf Multiprozessor-Systemen benutzen verschiedene CPUs einer Maschine, mit Leistungssteigerung + Man gewinnt Leistung: Kontextwechsel ist viel schneller – Man verliert evtl. an Transparenz: der Anwender ist zuständig für die korrekte, konfliktfreie nebenläufige Ausführung Prof. Dr. Jan Dünnweber, Folie 3 von 27 Verteilte Systeme Prof. Dr. Jan Dünnweber, Folie 4 von 27 Verteilte Systeme Implementierung von Threads Threads in Java I: Erben von Klasse Thread Ein Thread-Paket kann zweierlei implementiert werden Implementierung als Thread-Bibliothek auf Benutzerebene ◮ ◮ ◮ Einfach/billig Threads zu erzeugen/zerstören Ein Kontextwechsel ist schnell (wenige Anweisungen) Ein blockierender Aufruf (z. B. I/O) in einem Thread blockiert alle anderen Threads des gleichen Prozesses Implementierung im Kernel des Betriebssystems: ◮ ◮ Keine Blockierung, dafür aber ist für jede Thread-Operation ein Systemaufruf erforderlich Kontextwechsel fast genauso teuer wie für einen Prozess Aktuell wird hybride Lösung favorisiert, mit LWP (Light-Weight Processes): eine Mischung aus Benutzerebene und BS-Kernel. Java Threads Java ist erste universelle Programmiersprache, die Threads direkt auf Sprachebene unterstützt Prof. Dr. Jan Dünnweber, Folie 5 von 27 Verteilte Systeme Implementierung von Java Threads Wie auch viele andere Konzepte in der Java-Welt, werden Threads über Klassen bzw. Vererbung zur Verfügung gestellt Erste Methode der Thread-Deklaration in einem Java Programm: Die Java-Klasse Thread beinhaltet eine (ursprünglich leere) Methode run() Die Benutzer-Klasse MyThread wird als Unterklasse von Thread deklariert Die (vererbte) Methode run() wird in MyThread überschrieben Prof. Dr. Jan Dünnweber, Folie 6 von 27 Threads in Java I: Erben von Klasse Thread (Forts.) Verteilte Systeme Threads in Java II: Implementieren von Runnable Nachteil der ersten Methode: Abgeleitete Klasse (wie MyThread) darf von keiner anderen Klasse erben, da Java die Mehrfachvererbung verbietet class MyThread extends Thread { public void run() { ... } } ... Thread a = new MyThread(); Thread run(){} Zweite Methode der Thread-Deklaration in Java Programmen: Java-Interface Runnable enthält abstrakte Methode run() Die Benutzer-Klasse MyRunnable implementiert Runnable, indem sie die Methode run() definiert MyThread run(){...} Merke: die Java-Klasse Thread implementiert Runnable ebenfalls Vor- und Nachteile der zweiten Methode: + Kann von anderen Klassen erben Klassendiagramm in UML-Notation Unif. Modeling Lang. Prof. Dr. Jan Dünnweber, Folie 7 von 27 Verteilte Systeme – Kann Instanzmethoden von Thread nicht direkt benutzen. Umweg: über die Klassenmethode currentThread() erhält man die Referenz auf den aktuellen Thread in der run-Methode Prof. Dr. Jan Dünnweber, Folie 8 von 27 Verteilte Systeme Zweite Methode: UML Diagramm und Java-Deklaration Threads in Java I und II: Erzeugen und Starten Ein Thread ist immer ein Objekt der Klasse Thread und wird (wie jedes Java-Objekt) mit new von einem Konstruktor erzeugt: Runnable Bei der ersten Methode: Thread a = new MyThread(); run(){} Bei der zweiten Methode: MyRunnable target Thread a = new Thread(new MyRunnable()); oder (in der Klasse MyRunnable): Thread a = new Thread(this); Thread run(){...} Z.B. für ein Panel: In beiden Fällen kann der Konstruktor ein String-Argument haben (der Name des Threads, wird zum Debuggen benutzt); sonst bekommen die Threads ihre Namen vom System in der Erzeugungsreihenfolge: Thread-1, Thread-2, ... class MyRunnable extends java.awt.Panel implements Runnable { public void run() { ... } } ... Thread a = new Thread(new MyRunnable()); Beachte: Ein erzeugter Thread existiert zwar, läuft aber nicht! Thread muß in beiden Fällen explizit gestartet werden: a.start(); was die Ausführung seiner run() Methode veranlaßt. Prof. Dr. Jan Dünnweber, Folie 9 von 27 Verteilte Systeme Threads in verteilen Systemen: Client Threads erlauben blockierende Systemaufrufe, ohne dass der gesamte Prozess blockiert wird In einem verteilten System erlaubt dies: ◮ ◮ Kommunikation über mehrere logische Verbindungen Das Verbergen langer Latenzzeiten in Weitverkehrsnetzen (Latenzen bis in den Sekundenbereich sind üblich) Beispiel – Web-Browser: ◮ ◮ ◮ ◮ Es wird versucht, mit dem Anzeigen einer HTML-Seite anzufangen, noch bevor sie voll übertragen wurde So kommen Textsegmente früher als die Bilder Multithreaded Browser: es werden separate Threads aktiviert, jeder richtet eine separate Verbindung zum Server ein und empfängt die Daten So ein Multithreaded-Client ist besonders vorteilhaft, wenn der Webserver über mehrere Maschinen repliziert ist: jeder Thread kommuniziert mit eigener Replik Prof. Dr. Jan Dünnweber, Folie 11 von 27 Verteilte Systeme Prof. Dr. Jan Dünnweber, Folie 10 von 27 Verteilte Systeme Client: Benutzeroberflächen Benutzeroberfläche (GUI) – eine der wichtigsten Funktionen bei einem Client Manchmal ist die Benutzeroberfläche in die Hardware integriert, z. B. Display+Tastatur in Handys, evtl. Spacherkennung, etc. Die Oberfläche verbirgt vom Benutzer, dass verschiedene Anwendungsteile von versch. Programmen verarbeitet werden; ein Teil geändert ⇒ andere werden benachrichtigt Multithreading ist ein elementarer Bestandteil beinahe jeder Benutzeroberfäche. Warum? ⇒ a closer look at Java Swing ... Prof. Dr. Jan Dünnweber, Folie 12 von 27 Verteilte Systeme Java Swing - Die Welt in der wir uns bewegen Multithreading & Swing: Einführendes Beispiel Animation mit interaktiver Kontrolle Java Maskottchen “Duke” © by Oracle Corp. Technologie: Swing, die standard Java Bibliothek für graphical user interfaces (GUIs) Swing GUIs sind plattformunabhängig Prinzip: Ableiten von Basis-Komponenten (Widgets) & Überschreiben anwendungsspezifischer Details Start/Stop-Button: Würfel drehen oder anhalten Reset-Button: Würfel auf Ausgangsposition Schieberegler: Geschwindigkeit oder Drehrichtung ändern Alternativen: Qt/Gnome (Linux), Motif (Solaris), Aqua/Cocoa & Quartz (Mac), MFC (Windows), AWT © Oracle Swing & Multithreading: Warum programmiert man GUIs multi-threaded und was ist dabei zu beachten? 1 Responsivität: GUI darf nicht blockieren 2 Ereignisverarbeitung: GUIs sind implizit multithreaded 3 Thread-Sicherheit: Threads dürfen sich gegenseitig nicht behindern ⇒ Hierfür ist der Programmierer verantwortlich Prof. Dr. Jan Dünnweber, Folie 13 von 27 Verteilte Systeme Prof. Dr. Jan Dünnweber, Folie 14 von 27 ... zurück zum Beispiel: Ein Blick auf die Architektur Beim Design der Beispielanwendung wurde das Architektur-Pattern Model-View-Controller umgesetzt Direkte Referenzen: Event-Dispatching Main ThreadThread Controller→Model, Controller→View und Controller HochschuleMain.java View→Model. r e en Indirekter Zugriff von st d() i e L ge ng an ha Model→View und Ch etC s JPanel View→Controller. repaint() View Model Dashboard.java RedCube.java Multithreading in allen Komponenten User Thread User Thread Thread Event-Dispatching Ein- und Ausgabe-Logik im Event Dispatching Thread Vorteil: Austauschbare Kompenenten, z. B. verschiedene Views und Controller in Swing- und Web-Oberfläche für das gleiche Model Darstellung (User Thread) läuft unabhängig von der Verarbeitung von Programm- und Systeminteraktionen Prof. Dr. Jan Dünnweber, Folie 15 von 27 Verteilte Systeme Nur bei paralleler Verarbeitung von Programmund Benutzeraktivität gelingt das Zusammenspiel Daher im Folgenden intensive Architekturund Code-Analyse (7 slides of code ...) Verteilte Systeme deep dive into the code: Wie funktioniert sowas? Der Konstruktor der Controller-Klasse (HochschuleMain) initialisiert View (Dashboard) & Model (RedCube): public class HochschuleMain implements ActionListener, ChangeListener { public static final int WIDTH = 360, HEIGHT...; public static final String TITLE ="HS München"; private int width, height, inset; private Dashboard controls; private RedCube cube; public HochschuleMain(Container content) { this.inset = INSET;this.width = WIDTH; ... cube = new RedCube(width, height); controls = //this = ActionListener & ChangeListener new Dashboard( content, cube, this, this ); controls.setup( ); } ... Prof. Dr. Jan Dünnweber, Folie 16 von 27 Verteilte Systeme (1/7) Controller continued: Implizites Multithreading (2/7) actionPerformed und stateChanged Schnittstellen-Methoden zur Verarbeitung von Eingaben im Event Dispatching Thread: public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run( ) { JFrame frame = new JFrame(TITLE); Container content = frame.getContentPane( ); HochschuleMain rotation = new HochschuleMain(content); frame.setSize( ... ); ... frame.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE ); ... new Thread(rotation.cube).start(); rotation.cube.stopRotation( );// start resting ... frame.setVisible(true); } }); } public void stateChanged(ChangeEvent slide) { cube.setVelocity(controls.getVelocity( )); } Verteilte Systeme View-Komponente Dashboard: Widget Layout Komponenten-Setup, inklusive Hinzufügen der Listener: public class Dashboard extends GridBagLayout { final static String START = "Start", ...; // const private JSlider velocity; ... // member variables public Dashboard(Container c, RedCube cube, ActionListener buttons, ChangeListener adjuster) { this.content = c; this.cube = cube; velocity = new JSlider(..); start = new JButton(..); constraints = new GridBagConstraints( ); start.addActionListener(buttons); ... velocity.addChangeListener(adjuster); ... } public void setup() { content.setLayout(this); constraints.fill = GridBagConstraints.BOTH; ... ...content.add(cube, constraints);...}... Prof. Dr. Jan Dünnweber, Folie 19 von 27 Verteilte Systeme (3/7) Die invokeLater-Methode der SwingUtilities macht das Setup explizit zu einer Aufgabe des Event Dispatching Threads: public void actionPerformed(ActionEvent push) { JButton button = (JButton)pushed.getSource( ); String label = button.getText( ); if (label.equals(Dashboard.START)) { button.setText(Dashboard.STOP); cube.resumeRotation( ); } else if (label.equals(Dashboard.STOP)) { button.setText(Dashboard.START); cube.stopRotation( ); } else if (label.equals(Dashboard.RESET)) { cube.reset( ); } else System.exit(0); } Prof. Dr. Jan Dünnweber, Folie 17 von 27 Hauptprogramm: Die main-Methode Prof. Dr. Jan Dünnweber, Folie 18 von 27 (4/7) Verteilte Systeme Business Logic: Das Model, RedCube in Java (5/7) Logo laden, Kachel mit dem Würfel mittels Advanced Imaging API (JAI) ausschneiden und für die Rotation vorbereiten: import javax.media.jai.JAI; // ... class RedCube extends JPanel implements Runnable { final static int DEFAULT_ANGLE = −22, ...; // const private int width, height, inset, angle...;// members public RedCube(int width, int height) { velocity = DEFAULT_VELOCITY; ... paused = new ReentrantLock( ); // deadlock−save running = paused.newCondition( ); // thread control ... b = ImageIO.read(new File(SOURCE_FILE)); ... TiledImage cubeData = // extract cube data source.getSubImage(cubeOrigin, ... cubeSize); cubeSettings = new ParameterBlockJAI("rotate"); ... cubeSettings.addSource(cubeData); setDoubleBuffered(true); } Prof. Dr. Jan Dünnweber, Folie 20 von 27 Verteilte Systeme Parallele Berechnung und Darstellung (6/7) Die Methode paintComponent läuft im Event Dispatching Thread während die run-Methode explizit im User Thread läuft: public void paintComponent(Graphics handle) { handle.drawImage( background, ... ); cubeSettings.setParameter("angle", ... angle)); RenderedOp op = JAI.create("rotate", cubeSettings); cube = op.getRendering( ).getAsBufferedImage( ); ... handle.drawImage(cube ... cubeSize); } public void run( ) { for ( ; ; ) { while (suspended) { paused.lock( ); running.awaitUninterruptibly( ); paused.unlock( ); } rotate(angle = angle + direction % 360); try { Thread.sleep(20 − velocity); } catch (InterruptedException e) { } } } ... Prof. Dr. Jan Dünnweber, Folie 21 von 27 Verteilte Systeme Was haben wir aus dem Quelltext des Beispiels gelernt? GUIs erfordern Programmiermethodik, die das Multithreading berücksichtigt: Komponenten sind i. A. nicht thread-safe! Grundregel für Swing: Generieren und manipulieren Sie alles ausschließlich innerhalb des Event Dispatching Thread! noch einfacher: Verwenden Sie niemals getGraphics! Innerhalb des Event Dispatching Thread laufen: 1 Die CallBack-Methoden von JComponent und abgeleiteten Klassen: paintComponent, paintBorder & paintChildren 2 Aktivitäten, die Sie mittels SwingUtilities steuern: invokeAndWait(Runnable activity): Synchron mit dem aufrufenden Thread, d.h. Folgeanweisungen warten invokeLater(Runnable activity): Asynchron, d. h. der aufrufende Thread arbeitet nebenläufig weiter Aktivieren Sie den Event Dispatching Thread mittels repaint (Ausnahme: bei heavyweight Komponenten → update verwenden) Prof. Dr. Jan Dünnweber, Folie 23 von 27 Verteilte Systeme ... und schließlich: Thread-Kontrolle im RedCube public void setVelocity(int velocity) { if (velocity >= DEFAULT_VELOCITY) { this.direction = DEFAULT_DIRECTION; this.velocity = velocity; } else { this.direction = −DEFAULT_DIRECTION; this.velocity = MAX_VELOCITY − velocity; public void rotate(int angle) { this.angle = angle; repaint( ); (7/7) } } } public void stopRotation( ) { paused.lock( ); suspended = true; paused.unlock( ); } public void resumeRotation( ) { paused.lock( ); suspended = false; running.signal( ); paused.unlock( ); } Prof. Dr. Jan Dünnweber, Folie 22 von 27 Verteilte Systeme AWT & Swing, heavyweight vs. lightweight Alle Swing Komponenten (JFrame, JPanel, ...) haben einen heavyweight-Ancestor im Abstract Window Toolkit (AWT) AWT-Komponenten (Frame, Panel, ...) sind mittels nativer UI-Ressourcen des zugrunde liegenden OS umgesetzt Mittels update lässt sich die Darstellung von Komponenten des AWT im Event Dispatching Thread (inkrementell) steuern Wie im Beispiel gezeigt, erfordert auch Swing eine Abstimmung mit dem Event Dispatching Thread Die Bedienung von Swing ist aber einfacher: 100%-Java lightweight-Komponenten Größere Auswahl als im AWT Mehr Features (Double-Buffering, Tooltips etc.) Anpassungen an heavyweight Komponenten erfordern Eingriffe in die Darstellungshierarchie (mittels super.paintComponent) Regel: Vermeiden Sie das Mischen von Komponententypen und bevorzugen Sie lightweight Komponenten Prof. Dr. Jan Dünnweber, Folie 24 von 27 Verteilte Systeme Mutual Exclusion in GUIs Anleitung: Locks & Conditions wie im Beispiel verwenden Im Beispiel wurde Thread-Safety mittels concurrent.locks anstelle von klassischen synchronized Monitoren realisiert Vorteil: concurrent.locks ermöglichen die differenziertere Vergabe von Sperren Alle Lock-Klassen implementieren die Schnittstelle: public interface Lock { void lock( ); void lockInterruptibly( ) throws ...; boolean tryLock(long time, TimeUnit unit); void unlock( ); Condition newCondition( ); } special features: Preemtion und Timeouts zur Deadlock-Vermeidung Locks sind etwas komplizierter zu bedienen, aber flexibler als die Monitormethoden der Klasse Object ◮ Keine Bindung an die Blockstruktur des Programms ◮ Es existieren nicht-blockierende Varianten des Wartens auf ein Lock-Objekt (polling): 1 boolean tryLock( ); 2 boolean tryLock(long time, TimeUnit u); Prof. Dr. Jan Dünnweber, Folie 25 von 27 Verteilte Systeme Zusammenfassung und Ausblick Swing ist ein high-level Framework für die GUI-Programmierung. Eine Alternative in Java ist das lower-level Abstract Window Toolkit (AWT) Ein zentraler Bestandteil der GUI-Programmierung ist das Multithreading. Ereignisse (z.B. Benutzereingaben) werden parallel zum Programmfortschritt verarbeitet Ein flexibles Architekturmuster ist Model-View-Controller. Bei der Implementierung kommt es u. a. auf die korrekte Thread-Koordination an. Im Beispiel wurde diese mittels concurrent.locks und Conditions (Deadlock-frei) realisiert Eine Portierung ins Web erfordert lediglich Anpassungen an View und Controller Prof. Dr. Jan Dünnweber, Folie 27 von 27 Verteilte Systeme Conditions funktionieren in Java wie wait & notify Die Freigabe von Conditions kann aber an mehrere, unterschiedliche Bedingungen geknüpft sein: public interface Condition { void await( ); void awaitUninterruptibly( ); ... void signal( ); void signalAll( ); } Vorsicht : Sind signal oder await nicht von lock und unlock-Aufrufen umschlossen, so folgt eine IllegalMonitorStateException! Prof. Dr. Jan Dünnweber, Folie 26 von 27 Verteilte Systeme