SlideShare uma empresa Scribd logo
1 de 39
Thread
Programmierung in Java
  und einige Synchronisationsmechanismen
Multi-Tasking bei
Betriebssystemen


Programme laufen
  quasi-parallel
2 Kerne   4 Kerne   24 Kerne
Sie lernen kennen

• Parallele Ausführung von Algorithmen
• Gleichzeitiger Zugriff auf Ressourcen
• Mechanismen zur Synchronisation
• Methode / Best Practices
Parallele Algorithmen

  Parallele              Parallele
Verarbeitung           Verarbeitung
von Aufgaben            von Daten
(task decomposition)   (data decomposition)
int[] data = initializeArray();

for( int i = 0; i<data.length; i++ )
{
   data[i] = data[i] * 2;
}
int[] data = initializeArray();

for( int i = 0; i<data.length; i++ )
{
   data[i] = data[i] * 2;
}
int[] data = initializeArray();

for( int i = 0; i<data.length; i++ )
{
   data[i] = data[i] * 2;
}


Kern #1
int[] data = initializeArray();

for( int i = 0; i<data.length; i++ )
{
   data[i] = data[i] * 2;
}

          0 1 2 3 4 5 6 7 8 9
Kern #1
int[] data = initializeArray();

for( int i = 0; i<data.length; i++ )
{
   data[i] = data[i] * 2;
}

          0 1 2 3 4 5 6 7 8 9
Kern #1
Kern #2
int[] data = initializeArray();

for( int i = 0; i<data.length; i++ )
{
   data[i] = data[i] * 2;
}

          0 1 2 3 4
Kern #1
Kern #2
import java.lang.Thread;

public class MyThread extends Thread
{
   public void run()
   {
      // Code an dieser Stelle wird
      // nebenläufig ausgeführt
      // sobald der Thread startet
   }
}

      Der Thread muss noch durch Aufruf der
       Methode start() gestartet werden.
public static void main( String[] args )
{
      MyThread t = new MyThread();

     t.start();

     System.out.println(„Thread läuft“);

     t.join();




}
public static void main( String[] args )
{
      MyThread t = new MyThread();

     t.start();

     System.out.println(„Thread läuft“);

     t.join();




}
Thread #1
public static void main( String[] args )
{
      MyThread t = new MyThread();

     t.start();

     System.out.println(„Thread läuft“);

     t.join();




}

                                           Zeit
Thread #1
public static void main( String[] args )
{
      MyThread t = new MyThread();

     t.start();

     System.out.println(„Thread läuft“);

     t.join();




}

                                           Zeit
Thread #1
public static void main( String[] args )
{
      MyThread t = new MyThread();

     t.start();

     System.out.println(„Thread läuft“);

     t.join();




}

                                           Zeit
Thread #1
public static void main( String[] args )
{
      MyThread t = new MyThread();

     t.start();

     System.out.println(„Thread läuft“);

     t.join();




}

                                           Zeit
Thread #1
public static void main( String[] args )
{
      MyThread t = new MyThread();
                                                  Thread #2
     t.start();                                     public void run()
                                                    {
     System.out.println(„Thread läuft“);

     t.join();

                                                    }

}

                                           Zeit
Thread #1
public static void main( String[] args )
{
      MyThread t = new MyThread();
                                                  Thread #2
     t.start();                                     public void run()
                                                    {
     System.out.println(„Thread läuft“);

     t.join();

                                                    }

}

                                           Zeit
Thread #1
public static void main( String[] args )
{
      MyThread t = new MyThread();
                                                  Thread #2
     t.start();                                     public void run()
                                                    {
     System.out.println(„Thread läuft“);

     t.join();

                                                    }

}

                                           Zeit
class MyThread extends Thread
{
    private int[] data;
    private int start;
    private int end;

    public MyThread( int[] data, int start, int end )
    {
       this.data = data;
       this.start = start;
       this.end = end;
    }

    public void run()
    {
       for( int i=start; i<end; i++ )
       {
           data[i] = data[i] * 2;
       }
    }
}
public static void main( String[] args )
{
     int[] data = initializeArray();
     int length = data.length;
     int half = length / 2;

    MyThread t1 = new MyThread( data, 0, half );
    MyThread t2 = new MyThread( data, half, length );

    t1.start();
    t2.start();

    t1.join();
    t2.join();
}
Vergleich der Performance
Vergleich der Performance
data.length   mit 1 Thread   mit 2 Threads
     100         2 ms            2 ms
    1000         5 ms            5 ms
   10,000        41 ms          31 ms
  100,000       414 ms          254 ms
 1,000,000      4130 ms        2093 ms
10,000,000     41288 ms        26739 ms
int[] data = new int[10];
data[0] = initialValue();

for( int i = 1; i<data.length; i++ )
{
   data[i] = data[i-1] + 1;
}
int[] data = new int[10];
data[0] = initialValue();

for( int i = 1; i<data.length; i++ )
{
   data[i] = data[i-1] + 1;
}

 index   0 1 2 3 4 5 6 7 8 9
 value   0 1   2   3 4 5 6 7   8 9
Paralleler Zugriff auf

gemeinsame Ressourcen
public class BankAccount
{
    private int balance;

    public void deposit( int amount )
    {
        int temp = this.balance + amount;
        this.balance = temp;
    }
}
public class BankAccount
       {
           private int balance;

               public void deposit( int amount )
               {
                   int temp = this.balance + amount;
                   this.balance = temp;
               }
       }

Step        Thread 1: deposit(100);         Thread 2: deposit(200);        balance

 1         temp = this.balance + amount;   temp = this.balance + amount;     0
 2             this.balance = temp;                                        100
 3                                             this.balance = temp;        200
public class BankAccount
{
    private int balance;

    public synchronized void deposit( int amount )
    {
        int temp = this.balance + amount;
        this.balance = temp;
    }
}
public class BankAccount
{
    private int balance;

       public synchronized void deposit( int amount )
       {
           int temp = this.balance + amount;
           this.balance = temp;
       }
}
Step    Thread 1: deposit(100);         Thread 2: deposit(200);        balance

 1     temp = this.balance + amount;                                    0
 2         this.balance = temp;                                        100
 3                                     temp = this.balance + amount;   100
 4                                         this.balance = temp;        300
public class BankAccount
{
    private int balance;

    public void deposit( int amount )
    {
        synchronized( this )
        {
           int temp = this.balance + amount;
           this.balance = temp;
        }
    }
}
     Die synchronized Anweisung erzeugt
     einen Lock auf dem angegebenen Objekt
    zur Absicherung einer kritischen Region.
public class BankAccount
{
    private int balance;

    public void deposit( int amount )
    {
        synchronized( this )
        {
           int temp = this.balance + amount;
           this.balance = temp;
        }
    }
}


Ein wartender Thread hat keine Kontrolle mehr.
Nur ein Thread kann die kritische Region betreten.
public class BankAccount
{
    private int balance;

    private Lock lock = new ReentrantLock();

    public void deposit( int amount )
    {
        boolean acquired = lock.tryLock( 500,
        TimeUnit.MILLISECONDS );

        if( acquired )
        {
           int temp = this.balance + amount;
           this.balance = temp;
           lock.unlock();
        }
        else
        {}
    }
}
Dead Lock
   Lock lock1 = new ReentrantLock();
   Lock lock2 = new ReentrantLock();

 Thread #1                 Thread #2
lock1.lock();           lock2.lock();

lock2.lock();           lock1.lock();

....                    ...

lock2.unlock();         lock1.unlock();

lock1.unlock();         lock2.unlock();
Was es sonst noch gibt
• Semaphore
  • erlauben einer festen Anzahl von Threads
    das Betreten einer kritischen Region
  • Connection Pools zu Datenbanken
• Conditions
  • erlauben die Koordination zwischen
    Threads
  • typisch beim Producer / Consumer Muster
Methode
•   Parallele Algorithmen
    •   Ohne Threads starten
    •   Laufzeit des Programms analysieren
    •   Kritische Bereiche verbessern durch parallele Verarbeitung
•   Paralleler Ressourcenzugriff
    •   Mit gröbster Synchronisation beginnen
    •   Programm analysieren: warten Threads zu lange auf
        Ressourcen?
    •   Kritische Bereiche verbessern durch feinere
        Synchronisationsverfahren und Zeitüberschreitungen
Zusammenfassung und Ausblick

• Parallele Algorithmen
• Paralleler Ressourcenzugriff
• Synchronisationsmechanismen
• Race Conditions, Dead Locks
• Verteilte Systeme
• Map Reduce Algorithmen

Java threading

  • 1. Thread Programmierung in Java und einige Synchronisationsmechanismen
  • 3. 2 Kerne 4 Kerne 24 Kerne
  • 4. Sie lernen kennen • Parallele Ausführung von Algorithmen • Gleichzeitiger Zugriff auf Ressourcen • Mechanismen zur Synchronisation • Methode / Best Practices
  • 5. Parallele Algorithmen Parallele Parallele Verarbeitung Verarbeitung von Aufgaben von Daten (task decomposition) (data decomposition)
  • 6. int[] data = initializeArray(); for( int i = 0; i<data.length; i++ ) { data[i] = data[i] * 2; }
  • 7. int[] data = initializeArray(); for( int i = 0; i<data.length; i++ ) { data[i] = data[i] * 2; }
  • 8. int[] data = initializeArray(); for( int i = 0; i<data.length; i++ ) { data[i] = data[i] * 2; } Kern #1
  • 9. int[] data = initializeArray(); for( int i = 0; i<data.length; i++ ) { data[i] = data[i] * 2; } 0 1 2 3 4 5 6 7 8 9 Kern #1
  • 10. int[] data = initializeArray(); for( int i = 0; i<data.length; i++ ) { data[i] = data[i] * 2; } 0 1 2 3 4 5 6 7 8 9 Kern #1 Kern #2
  • 11. int[] data = initializeArray(); for( int i = 0; i<data.length; i++ ) { data[i] = data[i] * 2; } 0 1 2 3 4 Kern #1 Kern #2
  • 12. import java.lang.Thread; public class MyThread extends Thread { public void run() { // Code an dieser Stelle wird // nebenläufig ausgeführt // sobald der Thread startet } } Der Thread muss noch durch Aufruf der Methode start() gestartet werden.
  • 13. public static void main( String[] args ) { MyThread t = new MyThread(); t.start(); System.out.println(„Thread läuft“); t.join(); }
  • 14. public static void main( String[] args ) { MyThread t = new MyThread(); t.start(); System.out.println(„Thread läuft“); t.join(); }
  • 15. Thread #1 public static void main( String[] args ) { MyThread t = new MyThread(); t.start(); System.out.println(„Thread läuft“); t.join(); } Zeit
  • 16. Thread #1 public static void main( String[] args ) { MyThread t = new MyThread(); t.start(); System.out.println(„Thread läuft“); t.join(); } Zeit
  • 17. Thread #1 public static void main( String[] args ) { MyThread t = new MyThread(); t.start(); System.out.println(„Thread läuft“); t.join(); } Zeit
  • 18. Thread #1 public static void main( String[] args ) { MyThread t = new MyThread(); t.start(); System.out.println(„Thread läuft“); t.join(); } Zeit
  • 19. Thread #1 public static void main( String[] args ) { MyThread t = new MyThread(); Thread #2 t.start(); public void run() { System.out.println(„Thread läuft“); t.join(); } } Zeit
  • 20. Thread #1 public static void main( String[] args ) { MyThread t = new MyThread(); Thread #2 t.start(); public void run() { System.out.println(„Thread läuft“); t.join(); } } Zeit
  • 21. Thread #1 public static void main( String[] args ) { MyThread t = new MyThread(); Thread #2 t.start(); public void run() { System.out.println(„Thread läuft“); t.join(); } } Zeit
  • 22. class MyThread extends Thread { private int[] data; private int start; private int end; public MyThread( int[] data, int start, int end ) { this.data = data; this.start = start; this.end = end; } public void run() { for( int i=start; i<end; i++ ) { data[i] = data[i] * 2; } } }
  • 23. public static void main( String[] args ) { int[] data = initializeArray(); int length = data.length; int half = length / 2; MyThread t1 = new MyThread( data, 0, half ); MyThread t2 = new MyThread( data, half, length ); t1.start(); t2.start(); t1.join(); t2.join(); }
  • 25. Vergleich der Performance data.length mit 1 Thread mit 2 Threads 100 2 ms 2 ms 1000 5 ms 5 ms 10,000 41 ms 31 ms 100,000 414 ms 254 ms 1,000,000 4130 ms 2093 ms 10,000,000 41288 ms 26739 ms
  • 26. int[] data = new int[10]; data[0] = initialValue(); for( int i = 1; i<data.length; i++ ) { data[i] = data[i-1] + 1; }
  • 27. int[] data = new int[10]; data[0] = initialValue(); for( int i = 1; i<data.length; i++ ) { data[i] = data[i-1] + 1; } index 0 1 2 3 4 5 6 7 8 9 value 0 1 2 3 4 5 6 7 8 9
  • 29. public class BankAccount { private int balance; public void deposit( int amount ) { int temp = this.balance + amount; this.balance = temp; } }
  • 30. public class BankAccount { private int balance; public void deposit( int amount ) { int temp = this.balance + amount; this.balance = temp; } } Step Thread 1: deposit(100); Thread 2: deposit(200); balance 1 temp = this.balance + amount; temp = this.balance + amount; 0 2 this.balance = temp; 100 3 this.balance = temp; 200
  • 31. public class BankAccount { private int balance; public synchronized void deposit( int amount ) { int temp = this.balance + amount; this.balance = temp; } }
  • 32. public class BankAccount { private int balance; public synchronized void deposit( int amount ) { int temp = this.balance + amount; this.balance = temp; } } Step Thread 1: deposit(100); Thread 2: deposit(200); balance 1 temp = this.balance + amount; 0 2 this.balance = temp; 100 3 temp = this.balance + amount; 100 4 this.balance = temp; 300
  • 33. public class BankAccount { private int balance; public void deposit( int amount ) { synchronized( this ) { int temp = this.balance + amount; this.balance = temp; } } } Die synchronized Anweisung erzeugt einen Lock auf dem angegebenen Objekt zur Absicherung einer kritischen Region.
  • 34. public class BankAccount { private int balance; public void deposit( int amount ) { synchronized( this ) { int temp = this.balance + amount; this.balance = temp; } } } Ein wartender Thread hat keine Kontrolle mehr. Nur ein Thread kann die kritische Region betreten.
  • 35. public class BankAccount { private int balance; private Lock lock = new ReentrantLock(); public void deposit( int amount ) { boolean acquired = lock.tryLock( 500, TimeUnit.MILLISECONDS ); if( acquired ) { int temp = this.balance + amount; this.balance = temp; lock.unlock(); } else {} } }
  • 36. Dead Lock Lock lock1 = new ReentrantLock(); Lock lock2 = new ReentrantLock(); Thread #1 Thread #2 lock1.lock(); lock2.lock(); lock2.lock(); lock1.lock(); .... ... lock2.unlock(); lock1.unlock(); lock1.unlock(); lock2.unlock();
  • 37. Was es sonst noch gibt • Semaphore • erlauben einer festen Anzahl von Threads das Betreten einer kritischen Region • Connection Pools zu Datenbanken • Conditions • erlauben die Koordination zwischen Threads • typisch beim Producer / Consumer Muster
  • 38. Methode • Parallele Algorithmen • Ohne Threads starten • Laufzeit des Programms analysieren • Kritische Bereiche verbessern durch parallele Verarbeitung • Paralleler Ressourcenzugriff • Mit gröbster Synchronisation beginnen • Programm analysieren: warten Threads zu lange auf Ressourcen? • Kritische Bereiche verbessern durch feinere Synchronisationsverfahren und Zeitüberschreitungen
  • 39. Zusammenfassung und Ausblick • Parallele Algorithmen • Paralleler Ressourcenzugriff • Synchronisationsmechanismen • Race Conditions, Dead Locks • Verteilte Systeme • Map Reduce Algorithmen

Notas do Editor

  1. \n
  2. Sie kennen bereits das Konzept von Multi-Tasking bei Betriebssystemen. Der Rechner hat nur einen Prozessor, aber trotzdem k&amp;#xF6;nnen sie mehrere Programme parallel ausf&amp;#xFC;hren. Das OS ist hierbei daf&amp;#xFC;r verantwortlich, die Ausf&amp;#xFC;hrung der Programme oder besser der Prozesse zu steuern und jedem Prozess eine Zeitscheibe zur Benutzung des Prozessors zu geben. \n
  3. Die Hardwareentwicklung geht weiter und heutzutage hat ein typischer Computer nicht mehr nur einen Prozessor sondern mehrere. Wenn die Prozessoren alle auf einem Chip verbaut sind, nennt man dies Kerne. Schon heutige Smartphones, jedenfalls die modernen haben zwei Kerne wie zum Bespiel nach neue iPhone 4S oder das iPad 2, im n&amp;#xE4;chsten Fr&amp;#xFC;hjahr bringt HTC das erste Smartphone mit 4 Kernen auf den Markt. Typische Laptops oder Desktop Rechner haben 2 bis 4 Kerne, und ein typischer Server hat 12 bis 64 Kerne, auch hier gibt es nat&amp;#xFC;rlich gr&amp;#xF6;&amp;#xDF;ere Varianten. Was bringt das nun? Auf der Ebene des Betriebssystems k&amp;#xF6;nnen mehrere Programme nun nicht nur quasi sondern wirklich parallel ablaufen, da die Kerne unabh&amp;#xE4;ngig von einander arbeiten. Die Frage ist nun, wie kann man als Programmierer einer Applikation davon profitieren, dass der Rechner evtl. mehr als einen Kern besitzt. Die Antwort ist Threading. Thread bedeutet so etwas wie Kontrollfaden und ist ein Konzept, um innerhalb eines Programmes Code quasi oder real parallel ausf&amp;#xFC;hren zu k&amp;#xF6;nnen. Und die Grundlagen daf&amp;#xFC;r m&amp;#xF6;chte ich ihnen in diesem Vortrag am Beispiel der Programmiersprache Java zeigen. \n
  4. Ich werde Ihnen eine kurze Einf&amp;#xFC;hrung in die Java Thread Programmierung geben und mich dabei mehr an praktischen Beispielen orientieren als die theoretischen Grundlagen zu erl&amp;#xE4;utern. Neben dem Hardware Argument der Kerne gibt es nat&amp;#xFC;rlich noch weitere Gr&amp;#xFC;nde, sich mit dem Thema zu besch&amp;#xE4;ftigen. Daf&amp;#xFC;r wird es ebenfalls Beispiele geben. Threading ist ein wichtiges Konzept f&amp;#xFC;r Software-Entwickler. Wenn selbst Smartphones demn&amp;#xE4;chst &amp;#xFC;ber Prozessoren mit mehreren Kernen verf&amp;#xFC;gen, sollte jeder Entwickler diese Konzepte kennen. Allerdings ist der Umgang mit Threads oder Nebenl&amp;#xE4;ufigkeit auch extrem schwierig und erfordert sehr genaues Arbeiten und sehr viel Erfahrung. Daher werde ich Ihnen an einem weiteren Beispiel zeigen, welche Probleme in Programmen mit Threads auftreten k&amp;#xF6;nnen und einige Mechanismen zeigen, wie man diese Probleme in den Griff bekommen kann. Ich kann Ihnen also an dieser Stelle nat&amp;#xFC;rlich lediglich einen &amp;#xDC;berblick geben. Man lernt, wie immer beim Programmieren, die Konzepte nicht durch Vorlesungen anwenden sondern in dem man es selbst ausprobiert und aus seinen Fehlern lernt. Gl&amp;#xFC;ck hat, wer einen erfahrenen Entwickler bei Problemen befragen kann. \n
  5. Parallel Algorithmen lassen sich nochmal in mehrere Kategorien aufteilen. Wenn in einem Programm mehrere Aufgaben zu erledigen sind, k&amp;#xF6;nnte man &amp;#xFC;berlegen, ob die nicht mehrere Aufgaben parallel abarbeiten l&amp;#xE4;sst. Wenn ich sie bitten w&amp;#xFC;rde, die Fenster zu &amp;#xF6;ffnen und sie bitten w&amp;#xFC;rde, das Licht anzuschalten, dann k&amp;#xF6;nnen sie offensichtlich unabh&amp;#xE4;ngig voneinander arbeiten. Und dann gibt es noch die parallele Verarbeitung von Daten. Nehmen wir an, jemand m&amp;#xFC;sste 100 Klausuren kontrollieren. Wir betrachten hier nur den Bereich der parallelen Verarbeitung von Datenstrukturen, weil daf&amp;#xFC;r die Beispiele einfacher sind. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. Die Klasse Thread in Java ist daf&amp;#xFC;r gemacht, um Programmteile parallel zu anderen ausf&amp;#xFC;hren zu lassen. Der Code muss in die run Methode verschoben werden, evtl. braucht man noch Zugriff auf Ressourcen, die dann dem Thread &amp;#xFC;ber einen Konstuktor mitgegeben werden m&amp;#xFC;ssen. \n
  27. Schauen wir uns jetzt an, was passiert, wenn man einen Thread starten m&amp;#xF6;chte. Wir haben wir das Hauptprogramm, das ebenfalls in einem Thread l&amp;#xE4;uft, der von der virtuellen Maschine f&amp;#xFC;r Java gestartet wurde. Man erzeugt eine neue Instanz der eigenen Thread Klasse und ruf die Methode start auf. Diese Methode blockiert nicht, d.h. sie kehrt nach sehr kurzer Zeit zum Aufrufer zur&amp;#xFC;ck, so dass sofort nach dieser Methode die Bildschirmausgabe zu sehen ist. Parallel dazu wird nun die Methode run des Threads aufgerufen und f&amp;#xE4;ngt an zu arbeiten. Dies passiert dann in einem zweiten Thread. Man malt zur Verdeutlichung des Verhaltens von Threads h&amp;#xE4;ufig Zeit-Diagramme. Jede Linie steht hierbei f&amp;#xFC;r einen Thread, der gerade aufgef&amp;#xFC;hrt wird. Beim Start eines Threads hier eine neue Linie abzweigt und parallel zu ersten weitergef&amp;#xFC;hrt. Wenn der Thread endet, zeigt die Linie wieder auf den Thread, von dem abgezweigt wurde. \n
  28. Schauen wir uns jetzt an, was passiert, wenn man einen Thread starten m&amp;#xF6;chte. Wir haben wir das Hauptprogramm, das ebenfalls in einem Thread l&amp;#xE4;uft, der von der virtuellen Maschine f&amp;#xFC;r Java gestartet wurde. Man erzeugt eine neue Instanz der eigenen Thread Klasse und ruf die Methode start auf. Diese Methode blockiert nicht, d.h. sie kehrt nach sehr kurzer Zeit zum Aufrufer zur&amp;#xFC;ck, so dass sofort nach dieser Methode die Bildschirmausgabe zu sehen ist. Parallel dazu wird nun die Methode run des Threads aufgerufen und f&amp;#xE4;ngt an zu arbeiten. Dies passiert dann in einem zweiten Thread. Man malt zur Verdeutlichung des Verhaltens von Threads h&amp;#xE4;ufig Zeit-Diagramme. Jede Linie steht hierbei f&amp;#xFC;r einen Thread, der gerade aufgef&amp;#xFC;hrt wird. Beim Start eines Threads hier eine neue Linie abzweigt und parallel zu ersten weitergef&amp;#xFC;hrt. Wenn der Thread endet, zeigt die Linie wieder auf den Thread, von dem abgezweigt wurde. \n
  29. Schauen wir uns jetzt an, was passiert, wenn man einen Thread starten m&amp;#xF6;chte. Wir haben wir das Hauptprogramm, das ebenfalls in einem Thread l&amp;#xE4;uft, der von der virtuellen Maschine f&amp;#xFC;r Java gestartet wurde. Man erzeugt eine neue Instanz der eigenen Thread Klasse und ruf die Methode start auf. Diese Methode blockiert nicht, d.h. sie kehrt nach sehr kurzer Zeit zum Aufrufer zur&amp;#xFC;ck, so dass sofort nach dieser Methode die Bildschirmausgabe zu sehen ist. Parallel dazu wird nun die Methode run des Threads aufgerufen und f&amp;#xE4;ngt an zu arbeiten. Dies passiert dann in einem zweiten Thread. Man malt zur Verdeutlichung des Verhaltens von Threads h&amp;#xE4;ufig Zeit-Diagramme. Jede Linie steht hierbei f&amp;#xFC;r einen Thread, der gerade aufgef&amp;#xFC;hrt wird. Beim Start eines Threads hier eine neue Linie abzweigt und parallel zu ersten weitergef&amp;#xFC;hrt. Wenn der Thread endet, zeigt die Linie wieder auf den Thread, von dem abgezweigt wurde. \n
  30. Schauen wir uns jetzt an, was passiert, wenn man einen Thread starten m&amp;#xF6;chte. Wir haben wir das Hauptprogramm, das ebenfalls in einem Thread l&amp;#xE4;uft, der von der virtuellen Maschine f&amp;#xFC;r Java gestartet wurde. Man erzeugt eine neue Instanz der eigenen Thread Klasse und ruf die Methode start auf. Diese Methode blockiert nicht, d.h. sie kehrt nach sehr kurzer Zeit zum Aufrufer zur&amp;#xFC;ck, so dass sofort nach dieser Methode die Bildschirmausgabe zu sehen ist. Parallel dazu wird nun die Methode run des Threads aufgerufen und f&amp;#xE4;ngt an zu arbeiten. Dies passiert dann in einem zweiten Thread. Man malt zur Verdeutlichung des Verhaltens von Threads h&amp;#xE4;ufig Zeit-Diagramme. Jede Linie steht hierbei f&amp;#xFC;r einen Thread, der gerade aufgef&amp;#xFC;hrt wird. Beim Start eines Threads hier eine neue Linie abzweigt und parallel zu ersten weitergef&amp;#xFC;hrt. Wenn der Thread endet, zeigt die Linie wieder auf den Thread, von dem abgezweigt wurde. \n
  31. Schauen wir uns jetzt an, was passiert, wenn man einen Thread starten m&amp;#xF6;chte. Wir haben wir das Hauptprogramm, das ebenfalls in einem Thread l&amp;#xE4;uft, der von der virtuellen Maschine f&amp;#xFC;r Java gestartet wurde. Man erzeugt eine neue Instanz der eigenen Thread Klasse und ruf die Methode start auf. Diese Methode blockiert nicht, d.h. sie kehrt nach sehr kurzer Zeit zum Aufrufer zur&amp;#xFC;ck, so dass sofort nach dieser Methode die Bildschirmausgabe zu sehen ist. Parallel dazu wird nun die Methode run des Threads aufgerufen und f&amp;#xE4;ngt an zu arbeiten. Dies passiert dann in einem zweiten Thread. Man malt zur Verdeutlichung des Verhaltens von Threads h&amp;#xE4;ufig Zeit-Diagramme. Jede Linie steht hierbei f&amp;#xFC;r einen Thread, der gerade aufgef&amp;#xFC;hrt wird. Beim Start eines Threads hier eine neue Linie abzweigt und parallel zu ersten weitergef&amp;#xFC;hrt. Wenn der Thread endet, zeigt die Linie wieder auf den Thread, von dem abgezweigt wurde. \n
  32. Schauen wir uns jetzt an, was passiert, wenn man einen Thread starten m&amp;#xF6;chte. Wir haben wir das Hauptprogramm, das ebenfalls in einem Thread l&amp;#xE4;uft, der von der virtuellen Maschine f&amp;#xFC;r Java gestartet wurde. Man erzeugt eine neue Instanz der eigenen Thread Klasse und ruf die Methode start auf. Diese Methode blockiert nicht, d.h. sie kehrt nach sehr kurzer Zeit zum Aufrufer zur&amp;#xFC;ck, so dass sofort nach dieser Methode die Bildschirmausgabe zu sehen ist. Parallel dazu wird nun die Methode run des Threads aufgerufen und f&amp;#xE4;ngt an zu arbeiten. Dies passiert dann in einem zweiten Thread. Man malt zur Verdeutlichung des Verhaltens von Threads h&amp;#xE4;ufig Zeit-Diagramme. Jede Linie steht hierbei f&amp;#xFC;r einen Thread, der gerade aufgef&amp;#xFC;hrt wird. Beim Start eines Threads hier eine neue Linie abzweigt und parallel zu ersten weitergef&amp;#xFC;hrt. Wenn der Thread endet, zeigt die Linie wieder auf den Thread, von dem abgezweigt wurde. \n
  33. Schauen wir uns jetzt an, was passiert, wenn man einen Thread starten m&amp;#xF6;chte. Wir haben wir das Hauptprogramm, das ebenfalls in einem Thread l&amp;#xE4;uft, der von der virtuellen Maschine f&amp;#xFC;r Java gestartet wurde. Man erzeugt eine neue Instanz der eigenen Thread Klasse und ruf die Methode start auf. Diese Methode blockiert nicht, d.h. sie kehrt nach sehr kurzer Zeit zum Aufrufer zur&amp;#xFC;ck, so dass sofort nach dieser Methode die Bildschirmausgabe zu sehen ist. Parallel dazu wird nun die Methode run des Threads aufgerufen und f&amp;#xE4;ngt an zu arbeiten. Dies passiert dann in einem zweiten Thread. Man malt zur Verdeutlichung des Verhaltens von Threads h&amp;#xE4;ufig Zeit-Diagramme. Jede Linie steht hierbei f&amp;#xFC;r einen Thread, der gerade aufgef&amp;#xFC;hrt wird. Beim Start eines Threads hier eine neue Linie abzweigt und parallel zu ersten weitergef&amp;#xFC;hrt. Wenn der Thread endet, zeigt die Linie wieder auf den Thread, von dem abgezweigt wurde. \n
  34. Schauen wir uns jetzt an, was passiert, wenn man einen Thread starten m&amp;#xF6;chte. Wir haben wir das Hauptprogramm, das ebenfalls in einem Thread l&amp;#xE4;uft, der von der virtuellen Maschine f&amp;#xFC;r Java gestartet wurde. Man erzeugt eine neue Instanz der eigenen Thread Klasse und ruf die Methode start auf. Diese Methode blockiert nicht, d.h. sie kehrt nach sehr kurzer Zeit zum Aufrufer zur&amp;#xFC;ck, so dass sofort nach dieser Methode die Bildschirmausgabe zu sehen ist. Parallel dazu wird nun die Methode run des Threads aufgerufen und f&amp;#xE4;ngt an zu arbeiten. Dies passiert dann in einem zweiten Thread. Man malt zur Verdeutlichung des Verhaltens von Threads h&amp;#xE4;ufig Zeit-Diagramme. Jede Linie steht hierbei f&amp;#xFC;r einen Thread, der gerade aufgef&amp;#xFC;hrt wird. Beim Start eines Threads hier eine neue Linie abzweigt und parallel zu ersten weitergef&amp;#xFC;hrt. Wenn der Thread endet, zeigt die Linie wieder auf den Thread, von dem abgezweigt wurde. \n
  35. Schauen wir uns jetzt an, was passiert, wenn man einen Thread starten m&amp;#xF6;chte. Wir haben wir das Hauptprogramm, das ebenfalls in einem Thread l&amp;#xE4;uft, der von der virtuellen Maschine f&amp;#xFC;r Java gestartet wurde. Man erzeugt eine neue Instanz der eigenen Thread Klasse und ruf die Methode start auf. Diese Methode blockiert nicht, d.h. sie kehrt nach sehr kurzer Zeit zum Aufrufer zur&amp;#xFC;ck, so dass sofort nach dieser Methode die Bildschirmausgabe zu sehen ist. Parallel dazu wird nun die Methode run des Threads aufgerufen und f&amp;#xE4;ngt an zu arbeiten. Dies passiert dann in einem zweiten Thread. Man malt zur Verdeutlichung des Verhaltens von Threads h&amp;#xE4;ufig Zeit-Diagramme. Jede Linie steht hierbei f&amp;#xFC;r einen Thread, der gerade aufgef&amp;#xFC;hrt wird. Beim Start eines Threads hier eine neue Linie abzweigt und parallel zu ersten weitergef&amp;#xFC;hrt. Wenn der Thread endet, zeigt die Linie wieder auf den Thread, von dem abgezweigt wurde. \n
  36. Schreiben wir also unseren Algorithmus entsprechend in einen Thread um. Sie erkennen hier die Schleife wieder, wobei wir nun den Start- und Endindex variabel machen. Die entsprechenden Ressourcen werden &amp;#xFC;ber einen Konstruktur in den Thread &amp;#xFC;bergeben. \n
  37. Das ist nat&amp;#xFC;rlich nur eine sehr einfache Variante, bei der wir davon ausgehen, dass 2 Kerne vorhanden sind. Allgemein sollte man ein Programm so schreiben, dass es mit beliebig vielen Kernen umgehen kann. Wir lassen diesen Aspekt jetzt hier mal weg. Aber auch daf&amp;#xFC;r gibt es Unterst&amp;#xFC;tzung durch die Programmiersprache Java. \n
  38. Ein wichtiger Punkt bei der Benutzung von Threading ist es, die Performance zwischen den M&amp;#xF6;glichkeiten mit und ohne Thread zu arbeiten, zu vergleichen. Wenn sich f&amp;#xFC;r die konkreten Anwendungsf&amp;#xE4;lle keine Performance-Steigerung zeigen sollte, war die Verwendung von Thread unn&amp;#xF6;tig und sollte auch wieder ausgebaut werden. Warum sollte &amp;#xFC;berhaupt eine Thread Variante schlechter sein als die ohne Threads? Nun, das Erzeugen und Starten eines Threads ist eine sehr teure Operation, deren Aufwand durch die anschlie&amp;#xDF;ende parallele Ausf&amp;#xFC;hrung erstmal wieder kompensiert werden muss. Wenn also der Aufwand innerhalb der Threads klein ist, lohnt die Verwendung von Threads nicht. In unserem Beispiel k&amp;#xF6;nnen wir den Aufwand vergr&amp;#xF6;&amp;#xDF;ern, in dem wir das data Array gr&amp;#xF6;&amp;#xDF;er machen. Beachten Sie, dass die Operation * 2 nat&amp;#xFC;rlich sehr einfach ist. Wenn data also nur 10 Elemente umfasst, lohnt sich die Verwendung von Threads nicht. Sch&amp;#xE4;tzen Sie mal, bei welcher Array Gr&amp;#xF6;&amp;#xDF;e die Thread Variante schneller ist. Auf meinem Macbook mit 4 GB Hauptspeicher war es nicht m&amp;#xF6;glich, ein Array zu erzeugen, bei dem die 2-Thread Variante schneller l&amp;#xE4;uft. Um also einen positiven Effekt zu sehen, m&amp;#xFC;ssten wir den Aufwand innerhalb der Schleife vergr&amp;#xF6;&amp;#xDF;ern, zum Beispiel in dem wir die Operation * 2 durch eine komplexere Operation ersetzen. Ich habe mal zur Demonstration diese eine * 2 Operation durch 100 * 2 Operationen ersetzt und bekomme dann folgende Ergebnisse. Tabelle zeigen. \n\n\n
  39. Das folgende Beispiel zeigt eine &amp;#xE4;hnliche Schleife wie zuvor. Der Wert einer Array Elementes berechnet sich jetzt aus dem Array Element davor. Visualisierung des Verhaltens bei Annahme, dass das erste Element den Wert 0 bekommt und die Funktion foo immer 1 zur&amp;#xFC;ckliefert. Frage an die Studenten: Kann man diese Schleife ebenso einfach in zwei Threads parallel laufen lassen? Nein, das geht nicht, weil der zweite Thread ja bei Element 5 starten w&amp;#xFC;rden, aber der Wert in Element 4 noch nicht berechnet wurde. Trotzdem kann man auch diesen Algorithmus parallel schreiben, allerdings muss man ihn grunds&amp;#xE4;tzlich anders strukturieren. \n
  40. Das folgende Beispiel zeigt eine &amp;#xE4;hnliche Schleife wie zuvor. Der Wert einer Array Elementes berechnet sich jetzt aus dem Array Element davor. Visualisierung des Verhaltens bei Annahme, dass das erste Element den Wert 0 bekommt und die Funktion foo immer 1 zur&amp;#xFC;ckliefert. Frage an die Studenten: Kann man diese Schleife ebenso einfach in zwei Threads parallel laufen lassen? Nein, das geht nicht, weil der zweite Thread ja bei Element 5 starten w&amp;#xFC;rden, aber der Wert in Element 4 noch nicht berechnet wurde. Trotzdem kann man auch diesen Algorithmus parallel schreiben, allerdings muss man ihn grunds&amp;#xE4;tzlich anders strukturieren. \n
  41. Wir haben gerade Beispiele betrachtet, in denen wir die Datenstrukturen so aufgeteilt haben, dass mehrere Threads parallel und unabh&amp;#xE4;ngig voneinander arbeiten k&amp;#xF6;nnen. In der Praxis trifft man jedoch auch auf Beispiele, in denen Daten zwischen mehreren Threads geteilt werden. Das hat auch immer etwas mit Ressourcenbeschr&amp;#xE4;nkungen zu tun, es gibt also weniger Ressourcen als parallele Threads und somit m&amp;#xFC;ssen sich die Threads absprechen, wer auf die Ressourcen zugreifen darf, und wer nicht. \n\nStellen Sie sich vor zwei von Ihnen wollen gleichzeitig ein Buch aus der Bibliothek ausleihen, aber das Buch existiert nur einmal. Entweder wird durch eine Regel definiert, wer das Buch bekommt. Wer als erster kommt, mahlt zu erst. Oder sie m&amp;#xFC;ssen sich absprechen und es verhandeln. \n
  42. An diesem Beispiel den Begriff der Race Condition erkl&amp;#xE4;ren. \n\nWir sehen also, das Problem besteht darin, dass diese beiden Anweisungen nicht unterbrochen werden d&amp;#xFC;rfen. Wir m&amp;#xFC;ssen also sicherstellen, dass ein Thread, wenn er in die Methode deposit kommt, nicht unterbrochen werden darf. Man sagt auch dazu, dass wir diese Methode zu einer atomaren, also nicht mehr zu teilenden Operation machen m&amp;#xFC;ssen. \n\n
  43. An diesem Beispiel den Begriff der Race Condition erkl&amp;#xE4;ren. \n\nWir sehen also, das Problem besteht darin, dass diese beiden Anweisungen nicht unterbrochen werden d&amp;#xFC;rfen. Wir m&amp;#xFC;ssen also sicherstellen, dass ein Thread, wenn er in die Methode deposit kommt, nicht unterbrochen werden darf. Man sagt auch dazu, dass wir diese Methode zu einer atomaren, also nicht mehr zu teilenden Operation machen m&amp;#xFC;ssen. \n\n
  44. Das erreichen wir, wenn wir das Java Keyword synchronized an die Methode schreiben. Damit erreichen wir folgende Ausf&amp;#xFC;hrungsreihenfolge. Und damit auch das korrekte Ergebnis. Man nennt dies dann eine synchronized Methode. Um nun verstehen, was dabei im Hintergrund abgeht, schauen wir uns zun&amp;#xE4;chst eine andere Schreibweise an. \n
  45. Das erreichen wir, wenn wir das Java Keyword synchronized an die Methode schreiben. Damit erreichen wir folgende Ausf&amp;#xFC;hrungsreihenfolge. Und damit auch das korrekte Ergebnis. Man nennt dies dann eine synchronized Methode. Um nun verstehen, was dabei im Hintergrund abgeht, schauen wir uns zun&amp;#xE4;chst eine andere Schreibweise an. \n
  46. Eine semantisch &amp;#xE4;quivalente Schreibweise benutzt nicht eine synchronized Methode sondern eine synchronized Anweisung. Daf&amp;#xFC;r muss dann aber explizit angegeben werden, welches Objekt hier als Lock, d.h. als Schloss oder Sperre verwendet werden soll. Wenn wir das Beispiel von eben &amp;#xFC;bertragen, wird bei einer synchronized Methode das Objekt selbst, also this, als Sperre genommen. Wenn wir dies explizit hinschreiben, ist es gleichbedeutend. Sie k&amp;#xF6;nnen sich ein Lock wie ein Schloss oder ein Schild &amp;#x201E;do not disturb&amp;#x201C; an einer Hotelzimmert&amp;#xFC;r vorstellen. Wenn sie den Raum betreten, h&amp;#xE4;ngen sie das Schild raus, damit niemand anders hereinkommt. Wenn sie den Raum verlassen, nehmen sie das Schild wieder ab und andere k&amp;#xF6;nnen den Raum betreten. Man nennt diese Verfahrensweise einen gegenseitigen Ausschluss, weil nur ein Thread den Bereich betreten kann. Frage an die Studenten: Was passiert, wenn wir zwei Instanzen der Klasse BankAccount haben? Wird dann der gleiche Lock verwendet? Nein, es sind ja verschiedene Objekte, also verschiedene &amp;#x201E;this&amp;#x201C;. \n
  47. Diese Vorgehensweise mit snchronized Methoden oder synchronized Anweisungen ist sehr &amp;#xFC;blich und auch praktikabel in sehr vielen F&amp;#xE4;llen. Allerdings m&amp;#xF6;chte ich auf zwei Probleme hinweisen. Der Thread, der am Lock wartet hat keine Kontrolle mehr &amp;#xFC;ber seinen Wartezustand. Er wartet im schlimmsten Fall f&amp;#xFC;r immer. Wenn beispielsweise ein anderer Thread in eine Endlosschleife kommt. Oder weil viele anderen Threads an dem Lock warten und ein Thread sehr lange warten muss. \nHier k&amp;#xF6;nnte man auch den Begriff der Starvation erw&amp;#xE4;hnen. Starvation bedeutet Verhungern und beschreibt die Situation, in der ein Thread lange auf den Zutritt zu einer kritischen Region warten muss. \n\n
  48. Deswegen gibt es auch einen expliziten Umgang mit Locks. \n\nK&amp;#xF6;nnte schon allein deshalb gut sein, damit man zu lange Wartezeiten in Logs mitbekommt und diese auswerten kann. Rein praktisch k&amp;#xF6;nnte man hier zum Beispiel weitere Pr&amp;#xFC;fungen einbauen.\n
  49. Eine gro&amp;#xDF;e Gefahr bei der Programmierung mit Threads und Synchronisationsmechanismen ist die Verklemmung von Threads, auch Deadlock genannt. Bei einem Deadlock blockieren sich zwei Threads gegenseitig, weil jeder Thread auf den Zugriff auf eine Ressource wartet, die der andere Thread schon in Benutzung hat. Da keiner der Threads seine Resource freigibt, warten beide Threads, sie sind also tot. Deadlocks haben nat&amp;#xFC;rlich etwas mit Race Conditions zu tun, denn der Ablauf der Threads zueinander muss eine bestimmte Konstellelation haben, die unter Umst&amp;#xE4;nden nur selten im Programmablauf so eintritt. Das macht aber das Auffinden von Deadlocks schwer. Unter Umst&amp;#xE4;nden merkt man das erst im Produktionsbetrieb, wenn andere Situationen auftreteten, als man beim Testen vorhergesehen hat. Eine M&amp;#xF6;glichkeit zur Vermeidung von Deadlocks ist es, die Ressourecen immer in der gleichen Reihenfolge zu locken. \n
  50. Beispiele daf&amp;#xFC;r bringen, wann braucht man das?\n
  51. Erst mal das Programm ohne Parallelisierung schreiben und dann mit einem Profiler analyiseren. \nWenn sich dann Regionen zeigen, die sehr viel Zeit ben&amp;#xF6;tigen, dann sind das geeignete Kandidaten f&amp;#xFC;r eine Parallelisierung. Immer auf der h&amp;#xF6;chsten Ebenen anfangen!\nBeim parallelen Resourcenzugriff zun&amp;#xE4;chst so sicher wie m&amp;#xF6;glich arbeiten, dann analysieren und Bereiche, in denen Threads aufeinander warten, verbessern durch freingranulare Synchronisationen. \n\n
  52. \n