Praktikum Grundlagen der Programmierung - Woche 11 Yudhistira Arief Wibowo Materialien aus Folien von Andreas Stein und Julian Koch - LRZ Sync+Share

Die Seite wird erstellt Franz Böhme
 
WEITER LESEN
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