Thread-Konzept in objektorientierten Programmiersprachen 1 Threads ein Thread ist ein eigenständiges Programmfragment, das parallel zu anderen Teilen eines Programmes ablaufen kann alle Threads eines Programmes teilen sich einen gemeinsamen Adressraum (leichtgewichtiger Prozeß) in einem Multithreading-System verwaltet die Rechnerkernvergabe (Scheduler) die zur Ausführung anstehenden Threads man unterscheidet zwei Arten der Parallelität: pseudoparallele Abarbeitung auf Einprozessorsystemen echte parallele Abarbeitung auf Mehrprozessorsystemen 2 Threads in Java Threads werden in der virtuellen Maschine durch Objekte der Klasse javalangthread repräsentiert virtuelle Maschine verwendet Thread-Objekte zum Starten, Starten oder Suspendieren von Threads ein Thread beginnt die Ausführung mit dem Aufruf der run()- Methode des Thread-Objektes Deklaration eines Threads Konstruktion einer Unterklasse der Klasse Thread Implementierung des Interfaces Runnable Erzeugen eines Threads Erzeugen eines Objektes einer Unterklasse der Klasse der Thread Erzeugen eines Thread-Objektes unter Angabe eines Objektes einer Klasse, welches das Interface Runnable implementiert 3
Deklaration und Erzeugen eines Threads class TextThread extends Thread { String text; public TextThread(String text){ thistext = text; public void run() { for(int i = 0; i< 10; i++) Systemoutprintln(text); TextThread java = new TextThread( Java ); javastart(); TextThread expresso = new TextThread( Expresso ); expressostart(); 4 Deklaration und Erzeugen eines Threads class TextThread implements Runnable { String text; public TextThread(String text){ thistext = text; public void run() { while( true ) Systemoutprintln(text); TextThread java = new TextThread( Java ); Thread thread = new Thread(java); threadstart(); 5 Zustände eines Threads schlafend resume() suspend() suspendiert suspend() resume() blockiert interrupt() sleep() resume() suspend() notify(), interrupt() wait(), join() new erzeugt start() rechenwillig run() zu Ende stop() beendet Scheduler yield() rechnend seit JDK 12 verworfen 6
Starten eines Threads javastart(); Beenden eines Threads javastop(); Unterbrechen eines Threads javasuspend(); Fortsetzen eines Threads javaresume(); Pausieren eines Programmes try { javasleep(1000) // 1000 ms catch ( InterruptedException e) { Steuerung eines Threads 7 Steuerung eines Threads interrupt() - Wecken von schlafenden bzw blockierten Threads yield() - der aufrufende Thread gibt freiwillig die CPU ab isalive() - true, falls der Thread gestartet, aber nicht beendet ist join() - wartet auf das Ende des Threads, für den die Methode aufgerufen wurde setname(string) - ändert den Namen des Threads getname() - gibt den Namen des Threads aus setpriority(int) - ändert die Priorität des Threads ThreadMIN_PRIORITY = 0 ThreadMAX_PRIORITY = 10 (Standard - ThreadNORM_PRIORITY = 5) getpriority() - gibt die Priorität des Threads aus 8 Problem: Zugriff auf gemeinsame Daten Spooler- Katalog 4 abc out = 4 Thread A 5 6 progc progn Thread B 7 in = 7 9
Vermeidung von Wettkampfbedingungen Situationen, bei der zwei oder mehrere Threads auf gemeinsame Daten zugreifen und deren Ergebnisse vom Fortschritt eines Threads abhängen, heißen Wettkampfbedingungen Benötigt wird ein Verfahren zur Gewährleistung eines gegenseitigen Ausschlusses von Threads Programmteil aus dem auf den gemeinsamen Speicher zugegriffen wird heißt kritischer Bereich Vermeidung von Wettkampfbedingungen: 2 Threads dürfen sich nicht gleichzeitig in kritischen Bereichen aufhalten keine Annahme über relative Geschwindigkeit der Threads kein Threads außerhalb eines kritischen Bereiches darf einen anderen blockieren kein Thread sollte beliebig lange auf den Eintritt in einen kritischen Bereich warten 10 Synchronisationskonzept: Semaphor Mechanismus zum wechselseitigen Ausschluss und zur Synchronisation von Threads (Dijkstra, 1965) Ein Semaphor ist eine gemeinsam benutzte Integer-Variable S auf die nur die drei Operationen Init, P(asseer) und V(erlaat) zugreifen können P(S): if(s > 0) then S = S-1; else { stoppe ausführenden Thread; trage den Thread in die Warteliste von S ein; V(S): S = S + 1; if(warteliste von S!= ) then { wähle Thread Q aus Warteliste von S; springe zu der P-Operation in Q, durch die Q gestoppt wurde; System muss atomare Ausführung von P und V garantieren 11 Anwendung: Semaphor Definition kritischer Abschnitte: Sei S mit 1 initialisiert P(S); { kritischer Abschnitt V(S); Synchronisation von Threads: Sei S mit 0 initialisiert Thread A Thread B { { V(S); P(S); // B wartet auf A Initialisierungswert n > 1: Verwaltung kritischer Bereiche, die von n Threads gleichzeitig genutzt werden können 12
Beispiel: Producer und Consumer Threads Puffer Producer Consumer 0 1 2 3 4 abc cde efg out = 1 in = 4 13 Beispiel: Producer und Consumer Threads Semaphore nichtvoll = 5, // Puffer ist nicht voll, initialisiert mit Puffergröße 5 nichtleer = 0, // Puffer ist nicht leer gesperrt = 1; // Puffer wird gelesen oder beschrieben Producer: Consumer: do{ do{ Erzeuge Datum; P(nichtleer); P(nichtvoll); P(gesperrt); P(gesperrt); Nimm Datum aus Puffer; Lege Datum im Puffer ab; V(gesperrt); V(gesperrt); V(nichtvoll); V(nichtleer); Verbrauche Datum; until true; until true; 14 Probleme bei Semaphoren Falsche oder missbräuchliche Anwendung durch den Programmierer V(S); P(S); P(S); P(S); Auftreten von Verklemmungen (Deadlocks) P(S 1 ); P(S 2 ); P(S 2 ); P(S 1 ); V(S 2 ); V(S 1 ); V(S 1 ); V(S 2 ); Verklemmungen können durch Anforderung aller Semaphore vor Eintritt in den kritischen Bereich vermieden werden 15
Synchronisationskonzept: Monitor Semaphormechanismen für wechselseitigen Ausschluss und Synchronisation werden in einem Monitor (Hoare, Hansen) gekapselt Monitor ist ein abstrakter Datentyp mit der zusätzlichen Eigenschaft des wechselseitigen Ausschluss Deklaration gemeinsamer Variablen Deklaration von Operationen Initialisierungscode Eingabeschlange für Threads Bedingungsvariablen S mit denen über wait(s) und notify(s) zugegriffen werden kann 16 Monitore in Java Ein Monitor ist die Kapselung eines kritischen Bereiches mit Hilfe einer automatisch verwalteten Sperre jedes von der JVM angelegte Objekt verfügt über eine Sperre Schlüsselwort synchronized markiert Methoden in einer Klasse für die ein Thread vor Ausführung die Sperre verlangen muß class SpeechSynthesizer{ synchronized void say(string words){ // Speak Sperre wird immer von einem Objekt angefordert nicht statische Methode => Objekt mit dem die Methode aufgerufen wird statische Methode => Class-Objekt der entsprechenden Klasse 17 Beispiel class SpreadSheet { int cella1, cella2, cella3; synchronized int sumrow() { return cella1 + cella2 + cella3; synchronized void setrow(int a1, int a2, int a3){ cella1 = a1; cella2 = a2; cella3 = a3; Aufruf von sumrow() oder setrow() über ein Objekt a verlangt jeweils die Sperre von Objekt a 18
Beispiel class SpreadSheet { static int cella1, cella2, cella3; synchronized static int sumrow() { return cella1 + cella2 + cella3; synchronized static void setrow(int a1, int a2, int a3){ cella1 = a1; cella2 = a2; cella3 = a3; Aufruf von sumrow() oder setrow() verlangt jeweils die Sperre von dem Class-Objekt der Klasse SpreadSheet 19 Synchronisation von Blöcken Schlüsselwort synchronized kann für Blöcke verwendet werden Angabe eines Objektes, dessen Sperre verwendet werden soll synchronized (myobject){ // Anweisungsblock der synchronisiert werden muß synchronized void mymethod(){ void mymethod(){ <=> synchronized (this){ Synchronisation von Blöcken oder Methoden verschiedener Klassen 20 Freigabe von Sperren wait() und notify() erweitern Synchronisationsmechanismen Methodenaufruf wait() bewirkt: Thread beendet Bearbeitung eines synchronisierten Blocks und geht in den schlafenden Zustand Sperre für den synchronisierten Block wird freigegeben Methodenaufruf notify() bewirkt: mit wait() in den schlafenden Zustand versetzter Thread, kann wieder die Sperre des entsprechenden Objektes anfordern notify() aufrufender Thread beendet normal die Ausführung auf dem synchronisierten Block aufgeweckter Thread kann (muß aber nicht) als nächstes die Sperre erhalten und führt dann die Bearbeitung fort überladene Version der wait-methode und notifyall()-methode 21
Beispiel class MyThing { synchronized void waitermethod() { // Ausführung irgendwelche Sachen wait(); // Weiterbearbeitung der Methode synchronized void notifiermethod() { // Ausführung irgendwelcher Sachen notify(); // Weitere Arbeiten werden ausgeführt werden synchronized void relatedmethod(){ // Ausführung irgendwelcher Arbeiten 22 Beispiel: Producer und Consumer Threads class Producer extends Threads{ static final int MAXQUEUE = 5; private Vector messages = new Vector(); public void run(){ try{ while(true){ putmessage(); sleep( 1000); catch (InterruptedException e){ 23 Beispiel: Producer und Consumer Threads private sychronized void putmessage() throws InterruptedException{ while( messagesize == MAXQUEUE) wait(); messageaddelement( new javautildate()tostring()); notify(); public synchronized String getmessage() throws InterruptedException { notify(); while( messagessize() == 0 ) wait(); Stringmessage = (String) messagesfirstelement(); messagesremoveelement( message ); return message; 24
Beispiel: Producer und Consumer Threads class Consumer extends Threads { Producer producer; Consumer( Producer p){ producer = p; public void run() { try { while( true ){ String message = producergetmessage(); Systemoutprintln( Got message: + message); sleep( 2000 ); catch ( InterruptedException e ) { 25 Rechnerkernvergabe Rechnerkernvergabe (Scheduler) entscheidet welcher der lauffähigen Threads den Rechnerkern zugewiesen bekommt Rechnerkernvergabe sollte garantieren, dass jeder Thread einen gerechten Anteil an der CPU erhält die CPU zu 100 Prozent ausgelastet wird die Antwortzeit für interaktive Benutzer minimiert wird die Anzahl der bearbeiteten Aufträge pro Stunde maximiert werden Hauptproblem: Laufzeitverhalten der im System befindlichen Threads kann nicht vorausgesagt werden Rechnerkernvergabestrategien können in verdrängende und nicht verdrängende Strategien (non preemptive und preemptive scheduling) unterteilt werden 26 Nicht verdrängende Rechnerkernvergabestrategien FIFS-Strategie: Threads erhalten den Prozessor in der Reihenfolge ihres Eintretens in die Liste `rechenwillig Warteschlange RK A C E B Prioritätengesteuerte Rechnerkernvergabe: Warteschlangen Priorität RK A E 1 B 2 C 3 27
Verdrängende Strategien: Round-Robin-Verfahren jedem Thread wird eine Zeitscheibe zugewiesen, innerhalb welcher er laufen darf Umschalten der CPU Zeitscheibe ist abgelaufen Thread terminiert oder wird blockiert Rechnerkernvergabe verwaltet Liste rechenwilliger Threads Zeitscheibe abgelaufen Einfügen am Ende der Liste Festlegen der Dauer einer Zeitscheibe kleine Zeitscheibe geringe Effizienz der CPU durch viele Kontextumschaltungen große Zeitscheibe schlechte Antwortzeiten für interaktive Anforderungen Zeitscheibe um 100 ms ist häufig ein vernünftiger Kompromiss 28 Prioritätengesteuertes Round-Robin-Verfahren innerhalb der nach Prioritäten geordneten Warteschlangen wird das Round-Robin-Verfahren angewandt hoch Priorität tief 29 Methoden zur Rechnerkernvergabe in Java Prioritätengesteuerte Rechnerkernvergabe Rechnerkernabgabe sleep(), wait(), yields() und stop() 30