Praktikum Grundlagen der Programmierung - Woche 11 Yudhistira Arief Wibowo Materialien aus Folien von Andreas Stein und Julian Koch - LRZ Sync+Share
←
→
Transkription von Seiteninhalten
Wenn Ihr Browser die Seite nicht korrekt rendert, bitte, lesen Sie den Inhalt der Seite unten
Praktikum Grundlagen der Programmierung Woche 11 Yudhistira Arief Wibowo Materialien aus Folien von Andreas Stein und Julian Koch
Agenda • PrimzahlTestTest • Geschäftspartner • Threadpools • Parallele Streams (Optional) Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 2
Aufgabe 1 | PrimzahlTestTest • (Unit-) Testing Motivation • Eigene Tests schreiben • Assertions • Annotations • Mehr Beispiel Tests • Test Design Philosophie Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 3
Aufgabe 1 | PrimzahlTestTest ((Unit-) Testing Motivation) Ein Unit-test (zu dt. Modultest) dient dazu, einzelne Module einer Software auf korrekte Funktionalität zu testen. So möchte z.B. ein Games Engineer, der eine eigene Engine entwickelt, sicherstellen dass seine Vector3 Klasse fehlerfrei funktioniert und immer die korrekten Ergebnisse liefert. Um später, bei auftretenden Fehlern, diese Klasse als Fehlerquelle ausschließen zu können. Sie sehen rechts die Konzeption der zu testenden Klasse. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 4
Aufgabe 1 | PrimzahlTestTest (Eigene Tests schreiben) JUnit erlaubt es einem Programmierer Unit Tests für Java Programme zu schreiben. Tests werden auch in einer Klasse geschrieben (Bevorzugt hat diese im Namen „Test“). Ein Test ist dann eine public void Methode (meist ohne Argumente) welche mit @Test annotiert werden muss. Eine solche Test-Klasse kann beliebig viele Tests enthalten. class Vector3Test { @Test public void testGetter(){ float expectedX = 0; float expectedY = -1; float expectedZ = 1; Vector3 v = new Vector3(expectedX, expectedY, expectedZ); assertEquals(expectedX, v.getX()); // asserts that v.getX is equal to the expected Value assertEquals(expectedY, v.getY()); // analog to Y assertEquals(expectedZ, v.getZ()); // analog to Z } } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 5
Aufgabe 1 | PrimzahlTestTest (Asserstions) Um die Funktionalität des Programms zu überprüfen, müssen die Ergebnisse überprüft (asserted) werden. Hierzu gibt es viele verschiedene Methoden. Hier ein Überblick über die wichtigsten Methoden: • assertTrue(bool): Lässt den Test fehlschlagen wenn bool false ist • assertFalse(bool): Lässt den Test fehlschlagen wenn bool true ist • assertEquals(expected, actual): Lässt den Test fehlschlagen wenn actual nicht identisch zu expected ist • fail(): Erreicht die Testausführung diesen Aufruf wird der Test fehlschlagen Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 6
Aufgabe 1 | PrimzahlTestTest (Annotations) Im Gegensatz zu der bereits bekannten @Override Annotation (optional), müssen Tests immer annotiert werden um als solche erkannt zu werden! Die wichtigsten Annotationen: • @Test: Markiert eine Methode als Test • @BeforeEach: Markiert eine Methode die vor jedem Test ausgeführt wird • @BeforeAll: Markiert eine Methode die einmal vor allen Tests ausgeführt wird • @AfterEach: Markiert eine Methode die nach jedem Test ausgeführt wird • @AfterAll: Markiert eine Methode die einmal nach allen Tests ausgeführt wird BeforeEach, BeforeAll, AfterEach, AfterAll markieren keinen Test, sie werden meist dafür benutzt um Ressourcen für einen Test zu erstellen, zurückzusetzen, etc. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 7
Aufgabe 1 | PrimzahlTestTest (Beispiel) // Edgecase test with null @Test public void testAddNull() { Vector3 subject = new Vector3(0, 0, 0); try { subject.add(null); } catch (UnsupportedOperationException e) { return; } fail("No UnsupportedOperationException was thrown when adding null to a Vector!"); } // Simple positive and negative test for isNormalized @Test public void testIsNormalizedSimple() { Vector3 expectedTrue = new Vector3(1, 0, 0); Vector3 expectedFalse = new Vector3(1, 1, 1); assertTrue(expectedTrue.isNormalized()); assertFalse(expectedFalse.isNormalized()); } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 8
Aufgabe 1 | PrimzahlTestTest (Exceptions) Wie Sie an dem Beispiel erkennen konnten, dürfen Tests auch prüfen ob in bestimmten Fällen Exceptions geworfen werden (wenn diese auch erwartet werden). Um speziell auf Exceptions zu testen gibt es die assertThrows() Assertion. Diese erwartet zwei Argumente, zuerst die Klasse der erwarteten Exception (muss von Throwable erben) und ein Executable (Funktionales Interface). // edgecase test with null @Test public void testAddNull() { Vector3 subject = new Vector3(0, 0, 0); assertThrows(UnsupportedOperationException.class, () -> subject.add(null)); } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 9
Aufgabe 1 | PrimzahlTestTest (Test Design Philosophie) Tests sollten, wenn möglich, alle interessanten Edgecases und Situationen testen. Zudem sollten genügend positiv und negativ Tests (in verschiedenen Wertebereichen) geschrieben werden. Typische Edgecases sind: • maxValues/minValues • 0, -1, 1 • null • Leere Strings, Collections, etc. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 10
Aufgabe 1 | PrimzahlTestTest (Test Design Philosophie) Wenn ein Programmierer ein Softwaresystem entwickelt macht es Sinn, vor der richtigen Implementation der einzelnen Klassen, die Tests zu den Klassen zu schreiben. Damit diese ohne Compiler Errors geschrieben werden können, sollten alle Interfaces bereits existieren und, falls Klassen direkt getestet werden müssen, sollten alle Methoden vorhanden sein (die eben einen Standardwert wie null zurückgeben). Der Sinn die Tests zu erst zu schreiben rührt daher, dass man sich dadurch zunächst mit der Materie vertraut macht und aktiv überlegt, welche Fälle kritisch für die Implementation sein können. Ein weiterer Vorteil ist, dass der Programmierer die Implementation der Klasse an die Tests anpasst (Gut) und nicht die Tests im Nachhinein an die Implementation anpasst (Schlecht). Dieses Prinzip ist auch unter Test Driven Development (TDD) bekannt und ist eigentlich identisch zu dem Workflow den Sie durch die Artemis P-Aufgaben kennen. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 11
Aufgabe 1 | PrimzahlTestTest Bearbeiten Sie nun die Aufgabe „PrimzahlTestTest“ (Woche 11 P 01) auf Artemis. Fragen Sie bei Problemen bei der Ausführung der Tests ihren Tutor Arbeitszeit: ca. 40min Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 12
Aufgabe 2 | Geschäftspartner • Threads • Synchronisation Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 13
Aufgabe 2 | Geschäftspartner (Threads) Threads erlauben das nebenläufige Ausführen von Code. Dabei umfasst ein Thread einen Ausführungskontext; für uns relevant ist, dass dieser einen eigenen Program Counter sowie Stack besitzt (erinnern Sie sich für diese Begriffe an die JVM-Aufgaben zurück). Der Heap (also mit new allozierte Objekte) wird von Threads des gleichen Prozesses jedoch geteilt. In Java wird standardmäßig die main-Methode in einem Thread namens „main“ ausgeführt. Weitere Threads können mithilfe der Thread-Klasse erstellt werden. Zu diesem Zweck kann dem Konstruktor ein Runnable (meistens ein Lambda) übergeben werden1: var thread = new Thread(() -> { System.out.println("Hallo von Thread 2"); }); thread.start(); Um den Thread zu starten ist es zusätzlich nötig, auf diesem die start()-Methode aufzurufen. 1Auch kann von Thread die run-Methode überschrieben werden Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 14
Aufgabe 2 | Geschäftspartner (Threads) Nehmen Sie folgendes Programm an: var t = new Thread(() -> { System.out.println("T2"); }); System.out.println("T1 a"); t.start(); System.out.println("T1 b"); System.exit(0); Welche Ausgaben können hierbei entstehen? T1 a T1 a T2 T1 a T1 a T2 T1 b T1 a T1 b T2 T1 b T2 T1 b Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 15
Aufgabe 2 | Geschäftspartner (Threads) Nehmen Sie folgendes Programm an: var t = new Thread(() -> { System.out.println("T2"); }); System.out.println("T1 a"); t.start(); System.out.println("T1 b"); System.exit(0); Welche Ausgaben können hierbei entstehen? T1 a T1 a T2 T1 a T1 a T2 T1 b T1 a T1 b T2 T1 b T2 T1 b Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 16
Aufgabe 2 | Geschäftspartner Bearbeiten Sie DEN ERSTEN TEIL der Aufgabe „Geschäftspartner“ (Woche 11 P 02) auf Artemis. Gehen Sie mit Ihrem Tutor das aufkommende Problem durch. Arbeitszeit: ca. 20min Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 17
Aufgabe 2 | Geschäftspartner (Synchronisation) Ihnen ist das Phänomen der race conditions aufgefallen. Diese treten dann auf, wenn eine Variable zeitgleich modifiziert wird. Eine mögliche Lösung für dieses Problem sind die verschiedenen Synchronisationsmechanismen in Java, die es Threads ermöglichen, untereinander auf sich zu warten, bevor z.B. auf eine Variable zugegriffen wird. Hierzu dient das Keyword synchronized. Kritische Abschnitte (solche, die nicht nebenläufig ausgeführt werden dürfen) werden in einem synchronized-Block abgegrenzt: synchronized (m) { ++sum; } Das Objekt, welches hinter dem synchronized steht, ist dasjenige, auf dem synchronisiert wird (genauer gesagt wird auf dem Monitor des Objekts synchronisiert—mit jedem Objekt ist ein solcher assoziiert). Dies garantiert, dass in allen synchronized-Abschnitten des Objekts nur ein Thread gleichzeitig sein kann. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 18
Aufgabe 2 | Geschäftspartner (Synchronisation) Besteht eine Methode aus einem einzigen, großen kritischen Abschnitt, so kann die Methode als synchronized markiert werden. Hierbei wird implizit auf this synchronisiert. private int sum = 0; public synchronized void addOne() { ++sum; } public void alsoAddOne() { synchronized (this) { ++sum; } } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 19
Aufgabe 2 | Geschäftspartner Bearbeiten Sie den Rest der Aufgabe „Geschäftspartner“ (Woche 11 P 02) auf Artemis. Gehen Sie mit Ihrem Tutor das aufkommende Problem durch. Arbeitszeit: ca. 20min Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 20
Aufgabe 3 | Threadpools • Interrupts • Wait/Notify • Threadpools Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 21
Aufgabe 3 | Threadpools (Interrupts) Ein Thread kann von einem anderen über seine interrupt()-Methode unterbrochen werden. Diese setzt für den unterbrochenen Thread ein Status-Flag, welches über die statische Methode Thread.interrupted() abgefragt werden kann (dabei wird es sofort wieder gelöscht). var t = new Thread(() -> { while(!Thread.interrupted()){ /* do some work */} }); t.start(); // work t.interrupt(); Methoden, bei deren Aufruf der aktuelle Thread blockieren (warten) kann (z.B. Aufbau einer Internetverbindung), deklarieren in ihrem Methodenkopf oft, dass sie eine InterruptedException werfen können. Diese muss abgefangen werden und tritt dann auf, wenn der Thread im Zuge der Methode unterbrochen wird. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 22
Aufgabe 3 | Threadpools (Wait/Notify) Ein Thread kann sich schlafen legen, indem er auf einem Monitor (also beliebigen Objekt) die Methode wait() aufruft. Diese Methode blockiert solange, bis ein anderer Thread auf demselben Monitor entweder notifyAll() oder notify() aufruft. Falls mehrere Threads auf einem Monitor schlafen, werden bei notifyAll() alle, bei notify() jedoch nur einer aufgeweckt. Für alle Aufrufe gilt die Voraussetzung, dass sich der Aufruf in einem auf dem Monitor synchronisierten kritischen Abschnitt befindet (also in einem synchronized-Block). Hier wird auch gesagt, dass der Thread den Monitor besitzen muss (engl. own). Schauen Sie sich zur Verinnerlichung das Beispielprogramm auf der folgenden Folie an. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 23
Aufgabe 3 | Threadpools (Wait/Notify) private static class Sleeper extends Thread { @Override public void run(){ try { synchronized (this) { this.wait(); } } catch (InterruptedException e) {/*...*/} System.out.println("Thread 2 zu Ende"); } } public static void main(String[] args) throws InterruptedException { var sleeperThread = new Sleeper(); sleeperThread.start(); Thread.sleep(500); System.out.println("Thread.sleep() zu Ende"); synchronized (sleeperThread) { sleeperThread.notify(); } } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 24
Aufgabe 3 | Threadpools (Wait/Notify) • Wait/Notify wird oft dann genutzt, wenn ein Thread auf das Eintreten einer bestimmten Bedingung warten will (z.B. Datenstruktur ist nicht mehr leer). • In diesem Fall ist es notwendig, dass der schlafende Thread wait() in einer Schleife aufruft, die die Bedingung prüft, da er u.U. auch ohne den Aufruf von notify*() aufwachen kann. synchronized (monitor) { while (isEmpty()) { monitor.wait(); } } // Process next Element Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 25
Aufgabe 3 | Threadpools Ein Threadpool ist im Prinzip eine Menge an Worker-Threads, die einkommende Aufträge abarbeiten. Deren Nutzung ist oft effizienter als das Erstellen eines neuen Threads für jeden Auftrag, da die Thread-Verwaltung einen großen Overhead mit sich bringt. Threadpools können oft mit verschiedenen Parametern konfiguriert werden, beispielsweise der Anzahl der aktiven Worker-Threads (oder ob sich diese dynamisch verändern kann), die maximale Anzahl an unbearbeiteten (rückgestauten) Aufträgen (oder unbeschränkt), usw. Wir werden uns jedoch auf eine sehr simple Version beschränken. Beispielhafte Nutzung der in Java integrierten Threadpools: var threadPool = Executors.newCachedThreadPool(); threadPool.submit(() -> {/* work */}); threadPool.shutdown(); Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 26
Aufgabe 3 | Threadpools Bearbeiten Sie die Aufgabe „Threadpools“ (Woche 11 P 03) auf Artemis Arbeitszeit: ca. 60min Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 27
Aufgabe 4 | Parallele Streams (Überblick) Parallele Streams teilen die Bearbeitung des Streams auf mehrere Threads auf. Dadurch kann tendenziell eine schnellere Abarbeitung der Elemente erreicht werden. Jeder Stream kann durch das Keyword parallel() parallelisiert werden. Wie die Elemente aufgeteilt werden und wie viele Threads diese bearbeiten, ist bei parallelen Streams für den Programmierer nicht transparent. Das bedeutet, dass die Parallelisierung eines Streams die Berechnungszeit nicht ausnahmslos verbessert! Werden z.B. Intermediate Operations genutzt, die sich u.U.* schlecht parallelisieren lassen, wie limit(), kann die sequentielle Bearbeitung schneller sein. Auch spielt die Art der Stream-Source meist eine Rolle darin, wie gut sich ein Stream parallelisieren lässt. Bei der Parallelisierung muss zudem besonders acht auf die Ordnung der Elemente gegeben werden, wenn diese Relevant sein sollte. *siehe Folie 33 Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 28
Aufgabe 4 | Parallele Streams Die Folgenden Folien sind rein Optional und sind nicht Notwendig für das Verständnis der nächsten Aufgabe. Die Aufgabenstellung gibt genug Aufschluss über die Verwendung von Parallelen Streams! Auch wird in der Aufgabe die Unberechenbarkeit der tatsächlichen Performance Verbesserung durch Parallelisierung angesprochen! Es wird hier lediglich kurz auf die Idee und grob auf die ungeklärten Fragen aus Woche 9 hinsichtlich der Stream Flags eingegangen! Es kann, wenn gewünscht, zur Folie 34 gesprungen werden! Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 29
Aufgabe 4 | Parallele Streams Wie auch in Woche 9, sind die folgenden Folien größtenteils auf der IBM Java-Stream Serie aufgebaut! Zum besseren Verständnis ist es empfehlenswert, sich diese Quelle nochmal eigenständig durchzulesen! IBM: https://developer.ibm.com/articles/j-java-streams-3-brian-goetz/ Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 30
Aufgabe 4 | Parallele Streams (Idee) Ein paralleler Stream in Java kann die Ausführung der an sich unabhängigen Stream Operationen (z.B. map()) mithilfe eines Threadpools parallelisieren. Dies kann u.U. zu einer schnelleren Berechnung beitragen. Doch wie teilt Java den Stream in mehrere Teile auf, sodass mehrere Threads gleichzeitig die Elemente bearbeiten können? Eine Stream Source kann abstrahiert als sog. Spliterator beschrieben werden. Dieser übernimmt zwei Funktionen. Zum einen kann dieser über die Elemente der Stream Source iterieren, zum anderen kann dieser die Elemente der Quelle aufteilen (splitten) damit mehrere Threads diese bearbeiten können. *Auch die sequentiellen Streams arbeiten mit einem Spliterator, bloß wird hier nie versucht die Quelle aufzuteilen! Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 31
Aufgabe 4 | Parallele Streams (Stream Flags) In Woche 9 haben wir bereits die Stream Flags angesprochen, wobei wir das Ordered-Flag erstmal vernachlässigt haben. Stream Flags (Quelle: https://developer.ibm.com/articles/j-java-streams-3-brian-goetz/) Dieses Stream-Flag ist besonders interessant bei parallelen Streams. Es beschreibt ob die Reihenfolge (Ordered != Sorted) der Elemente der Quelle signifikant für die Berechnung ist. Die Reihenfolge ist z.B. interessant für Terminal-Operations wie forEachOrdered() und findFirst(). Nicht relevant ist diese für die meisten Akkumulationen, z.B. reduce(). Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 32
Aufgabe 4 | Parallele Streams (Stream Flags) Wenn der Stream das Ordered-Flag gesetzt hat, müssen alle Stream Operationen diese Reihenfolge respektieren (Achtung, Operationen wie sorted() setzen das Ordered-Flag). Für sequentielle Streams ist diese Bedingung kein Problem und somit praktisch kostenlos (da die Bearbeitung der Elemente gleich der „encounter order“ ist). Für parallele Streams bedeutet das respektieren einer Ordnung meist einen großen Overhead. Diese Kosten können bei Operationen wie reduce() meistens umgangen werden (Da hier die Ordnung der Elemente meist nicht signifikant ist). Es kann daher helfen das Ordered-Flag vor der Parallelisierung des Streams (durch parallel()) zu entfernen, durch die unordered() Intermediate-Operation. Ein kleines Beispiel über die Konsequenzen. Eine limit(30) Operation muss bei einem Ordered-Parallel-Stream garantieren dass die ersten 30 Elemente bearbeitet werden. Wenn hingegen das Ordered-Flag nicht gesetzt ist, muss diese Operation nur garantieren dass höchstens 30 Elemente bearbeitet werden (welche das sind, ist egal). Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 33
Aufgabe 4 | Parallele Streams Bearbeiten Sie die Aufgabe „Parallele Streams“ (Woche 11 P 04) auf Artemis Arbeitszeit: ca. 60min Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 02.02.2021 34
Fragen? Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 35
Sie können auch lesen