Commit 40d39745 authored by Blanke, Daniela's avatar Blanke, Daniela
Browse files

Update concurrency-threads.html

parent 49da0bcc
Pipeline #8034 passed with stage
in 1 minute and 14 seconds
...@@ -56,11 +56,11 @@ ...@@ -56,11 +56,11 @@
Thread thread = new Thread(runnable); Thread thread = new Thread(runnable);
thread.start();</code></pre> thread.start();</code></pre>
<p>Im folgenden Code-Beispiel werden in der Klasse <code>MyLoopThread</code> 3 Threads gestartet, die jeweils die Zahlen von 1 bis 100 hochzählen und ausgeben. Anhand der Ausgaben lässt sich das zufällige <i>Thread-Interleaving</i> beobachten. Es ist nicht vorhersehbar, welcher Thread zuerst fertig wird. In der Klasse <code>MyLoopSleepThread</code> wird das voherige Beispiel nur darum erweitert, dass jeder Thread nach der Ausgabe einer Zahl für 10 ms wartet (Zeilen 9-11). Durch diese bewusste Verzögerung zählen zwar alle Threads mit hoher Wahrscheinlichkeit im Takt, aber nicht garantiert. Die Korrektheit einer Anwendung sollte nie von einem derartigen Aufruf der Methode <code>sleep</code> abhängen.</p> <p>Im folgenden Code-Beispiel werden in der Klasse <code>MyLoopThread</code> 3 Threads gestartet, die jeweils die Zahlen von 1 bis 100 hochzählen und ausgeben. Anhand der Ausgaben lässt sich das zufällige <i>Thread-Interleaving</i> beobachten. Es ist nicht vorhersehbar, welcher Thread zuerst fertig wird. In der Klasse <code>MyLoopSleepThread</code> wird das vorherige Beispiel nur darum erweitert, dass jeder Thread nach der Ausgabe einer Zahl für 10 ms wartet (Zeilen 9-11). Durch diese bewusste Verzögerung zählen zwar alle Threads mit hoher Wahrscheinlichkeit im Takt, aber nicht garantiert. Die Korrektheit einer Anwendung sollte nie von einem derartigen Aufruf der Methode <code>sleep</code> abhängen.</p>
<ul class="nav nav-tabs" id="threads-tabs" role="tablist"> <ul class="nav nav-tabs" id="threads-tabs" role="tablist">
<li class="nav-item"><a href="#threads-tabs-loop" class="nav-link active" data-toggle="tab" role="tab">MyLoopThread</a></li> <li class="nav-item"><a href="#threads-tabs-loop" class="nav-link active" data-toggle="tab" role="tab">MyLoopThread</a></li>
<li class="nav-item"><a href="#threads-tabs-loop2" class="nav-link" data-toggle="tab" role="tab">MyLoopSleepThread</code></a></li> <li class="nav-item"><a href="#threads-tabs-loop2" class="nav-link" data-toggle="tab" role="tab">MyLoopSleepThread</a></li>
</ul> </ul>
<div class="tab-content" id="threads-tabs-content"> <div class="tab-content" id="threads-tabs-content">
<div class="tab-pane show active" id="threads-tabs-loop" role="tabpanel"> <div class="tab-pane show active" id="threads-tabs-loop" role="tabpanel">
...@@ -104,11 +104,11 @@ thread.start();</code></pre> ...@@ -104,11 +104,11 @@ thread.start();</code></pre>
}</code></pre></div> }</code></pre></div>
</div> </div>
<p>Ein <code>Thread</code> wird beendet, wenn er das Ende der Methode <code>run</code> des ausgeführten <code>Runnable</code> erreicht. Es ist durchaus üblich, dass Threads dauerhaft auf bestimmte Ereignisse warten und diese verarbeiten, bis eine Abbruchbedingung erfüllt wird. Das erste der beiden folgenden Code-Beispiele zeigt, wie ein derartiges sanftes, kontrolliertes Beenden eines Threads am Ende einer Iteration herbeigeführt werden kann. Das zweite Code-Beispiel zeigt dagegen, dass ein Thread auch abrupt mit sofortiger Wirkung von außen unterbrochen werden kann, indem die Methode <code>interrupt</code> auf dem <code>Thread</code>-Objekt ausgeführt wird. Die aktuelle Iteration wird hierbei nicht mehr vollständig ausgeführt, sondern unmittelbar abgebrochen. Methoden wie z.B. <code>sleep</code>, die einen Thread in einen wartenden Zustand versetzen, werfen i.d.R. eine <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/InterruptedException.html"><code>InterruptedException</code></a>, falls sie von einem anderen Thread von außen durch Aufruf der Methode <code>interrupt</code> unterbrochen worden sind.</p> <p>Ein <code>Thread</code> wird beendet, wenn er das Ende der Methode <code>run</code> des ausgeführten <code>Runnable</code> erreicht. Es ist durchaus üblich, dass Threads dauerhaft auf bestimmte Ereignisse warten und diese verarbeiten, bis eine Abbruchbedingung erfüllt wird. Das erste der beiden folgenden Code-Beispiele zeigt, wie ein derartiges sanftes, kontrolliertes Beenden eines Threads am Ende einer Iteration herbeigeführt werden kann. Das zweite Code-Beispiel zeigt dagegen, dass ein Thread auch abrupt mit sofortiger Wirkung von außen unterbrochen werden kann, indem die Methode <code>interrupt</code> auf dem <code>Thread</code>-Objekt ausgeführt wird. Die aktuelle Iteration wird hierbei nicht mehr vollständig ausgeführt, sondern unmittelbar abgebrochen. Methoden wie z.B. <code>sleep</code>, die einen Thread in einen wartenden Zustand versetzen, werfen i.d.R. eine <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/InterruptedException.html"><code>InterruptedException</code></a>, falls sie von einem anderen Thread von außen, durch Aufruf der Methode <code>interrupt</code>, unterbrochen worden sind.</p>
<ul class="nav nav-tabs" id="threads2-tabs" role="tablist"> <ul class="nav nav-tabs" id="threads2-tabs" role="tablist">
<li class="nav-item"><a href="#threads2-tabs-stop" class="nav-link active" data-toggle="tab" role="tab">Thread über Bedingung beenden</a></li> <li class="nav-item"><a href="#threads2-tabs-stop" class="nav-link active" data-toggle="tab" role="tab">Thread über Bedingung beenden</a></li>
<li class="nav-item"><a href="#threads2-tabs-interrupt" class="nav-link" data-toggle="tab" role="tab">Thread-Interruption</code></a></li> <li class="nav-item"><a href="#threads2-tabs-interrupt" class="nav-link" data-toggle="tab" role="tab">Thread-Interruption</a></li>
</ul> </ul>
<div class="tab-content" id="threads2-tabs-content"> <div class="tab-content" id="threads2-tabs-content">
<div class="tab-pane show active" id="threads2-tabs-stop" role="tabpanel"> <div class="tab-pane show active" id="threads2-tabs-stop" role="tabpanel">
...@@ -155,7 +155,7 @@ thread.start();</code></pre> ...@@ -155,7 +155,7 @@ thread.start();</code></pre>
}</code></pre></div> }</code></pre></div>
</div> </div>
<p>In Java können Threads eine Priorität zwischen 1 und 10 zugewiesen bekommen. Einem Thread wird je nach Priorität mehr oder weniger CPU-Zeit zugestanden. Das folgende Code-Beispiel führt 10 Threads mit jeweils unterschiedlicher Priorität aus (Zeilen 14-18). Jeder Thread belastet die CPU, indem er eine Variable inkrementiert (Zeilen 7-8). Nach 10 s werden alle Threads abgebrochen (Zeilen 20+33). Die Ausgabe in den Zeilen 23-31 zeigt exemplarisch, wie viel CPU-Zeit den Threads zugestanden worden ist. Bei jeder Ausführung wird die Ausgabe sich leicht unterscheiden. Es ist zu berücksichtigen, dass auf dem Betriebssystem weitere Prozesse ausgeführt werden, so dass die theoretisch verfügbar CPU-Zeit nicht allein auf die Java-Threads verteilt werden kann. Die Ausgabe könnte auf einem Computer mit 4 Prozessorkernen wie folgt aussehen:</p> <p>In Java können Threads eine Priorität zwischen 1 und 10 zugewiesen bekommen. Einem Thread wird je nach Priorität mehr oder weniger CPU-Zeit zugestanden. Das folgende Code-Beispiel führt 10 Threads mit jeweils unterschiedlicher Priorität aus (Zeilen 14-18). Jeder Thread belastet die CPU, indem er eine Variable inkrementiert (Zeilen 7-8). Nach 10 s werden alle Threads abgebrochen (Zeilen 20+33). Die Ausgabe in den Zeilen 23-31 zeigt exemplarisch, wie viel CPU-Zeit den Threads zugestanden worden ist. Bei jeder Ausführung wird die Ausgabe sich leicht unterscheiden. Es ist zu berücksichtigen, dass auf dem Betriebssystem weitere Prozesse ausgeführt werden, sodass die theoretisch verfügbar CPU-Zeit nicht allein auf die Java-Threads verteilt werden kann. Die Ausgabe könnte auf einem Computer mit 4 Prozessorkernen wie folgt aussehen:</p>
<pre> <pre>
CPU time of Thread-0: 187 ms CPU time of Thread-0: 187 ms
...@@ -210,7 +210,7 @@ Available processors: 4 ...@@ -210,7 +210,7 @@ Available processors: 4
<h4>Scheduling von Threads</h4> <h4>Scheduling von Threads</h4>
<p>Es ergibt sich häufig die Anforderung, dass ein Runnable nicht sofort sondern zu einem bestimmten Zeitpunkt in der Zukunft ausgeführt werden soll, oder dass ein Runnable periodisch in bestimmten Intervallen wiederholt werden soll. Eine derartige zeitliche Ablaufsteuerung von Prozessen bezeichnen wir allgemein als <i>Scheduling</i>. Im Folgenden werden zwei Varianten für das Scheduling von Runnables in Java gezeigt. In beiden Code-Beispielen entsteht bei Ausführung die folgende Ausgabe:</p> <p>Es ergibt sich häufig die Anforderung, dass ein Runnable nicht sofort, sondern zu einem bestimmten Zeitpunkt in der Zukunft ausgeführt werden soll, oder dass ein Runnable periodisch in bestimmten Intervallen wiederholt werden soll. Eine derartige zeitliche Ablaufsteuerung von Prozessen bezeichnen wir allgemein als <i>Scheduling</i>. Im Folgenden werden zwei Varianten für das Scheduling von Runnables in Java gezeigt. In beiden Code-Beispielen entsteht bei Ausführung die folgende Ausgabe:</p>
<pre>Timed event 1 12:01:02.689008600 <pre>Timed event 1 12:01:02.689008600
Timed event 2 12:01:03.673393100 Timed event 2 12:01:03.673393100
Timed event 3 12:01:04.673484800 Timed event 3 12:01:04.673484800
...@@ -219,7 +219,7 @@ Timed event 5 12:01:06.673461800</pre> ...@@ -219,7 +219,7 @@ Timed event 5 12:01:06.673461800</pre>
<ul class="nav nav-tabs" id="threads3-tabs" role="tablist"> <ul class="nav nav-tabs" id="threads3-tabs" role="tablist">
<li class="nav-item"><a href="#threads3-tabs-timer" class="nav-link active" data-toggle="tab" role="tab">TimerTask</a></li> <li class="nav-item"><a href="#threads3-tabs-timer" class="nav-link active" data-toggle="tab" role="tab">TimerTask</a></li>
<li class="nav-item"><a href="#threads3-tabs-executor" class="nav-link" data-toggle="tab" role="tab">ScheduledExecutorService</code></a></li> <li class="nav-item"><a href="#threads3-tabs-executor" class="nav-link" data-toggle="tab" role="tab">ScheduledExecutorService</a></li>
</ul> </ul>
<div class="tab-content" id="threads3-tabs-content"> <div class="tab-content" id="threads3-tabs-content">
<div class="tab-pane show active" id="threads3-tabs-timer" role="tabpanel"> <div class="tab-pane show active" id="threads3-tabs-timer" role="tabpanel">
...@@ -265,15 +265,15 @@ class MyScheduledExecutorService { ...@@ -265,15 +265,15 @@ class MyScheduledExecutorService {
<ul> <ul>
<li><b>Timer</b>: In Zeile 15 wird ein <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Timer.html"><code>Timer</code></a> erzeugt, über den mittels der Methode <code>scheduleAtFixedRate</code> ein <code>TimerTask</code>-Objekt, also eine zeitgesteuerte Aufgabe, wiederholt ausgeführt werden kann (Zeile 16). Das zweite Input-Argument gibt die Verzögerung (in ms) der ersten Ausführung an, das dritte Input-Argument das Intervall (in ms) für die wiederholte Ausführung. Die Ausführung endet erst mit dem Abbruch des <code>Timer</code> (Zeile 19). Die Klasse <code>TimerTask</code> implementiert das Interface <code>Runnable</code>.</li> <li><b>Timer</b>: In Zeile 15 wird ein <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Timer.html"><code>Timer</code></a> erzeugt, über den mittels der Methode <code>scheduleAtFixedRate</code> ein <code>TimerTask</code>-Objekt, also eine zeitgesteuerte Aufgabe, wiederholt ausgeführt werden kann (Zeile 16). Das zweite Input-Argument gibt die Verzögerung (in ms) der ersten Ausführung an, das dritte Input-Argument das Intervall (in ms) für die wiederholte Ausführung. Die Ausführung endet erst mit dem Abbruch des <code>Timer</code> (Zeile 19). Die Klasse <code>TimerTask</code> implementiert das Interface <code>Runnable</code>.</li>
<li><b>ScheduledExecutorService</b>: In Zeile 11 wird ein <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html"><code>ScheduledExecutorService</code></a> erzeugt, dem in diesem Fall genau ein Thread zur Ausführung der über ihn gesteuerten Runnables zur Verfügung steht. In Zeile 12 wird entsprechend des vorherigen Timer-Beispiels die wiederholte Ausführung eines Runnable gestartet. Die initiale Verzögerung beträgt wiederum 500 ms und das Intervall zwischen den Ausführungen 1 s. Der <code>ScheduledExecutorService</code> kann mittels der Methode <code>shutdown</code> beendet werden (Zeile 15).</li> <li><b>ScheduledExecutorService</b>: In Zeile 11 wird ein <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html"><code>ScheduledExecutorService</code></a> erzeugt, dem in diesem Fall genau ein Thread zur Ausführung der über ihn gesteuerten Runnables zur Verfügung steht. In Zeile 12 wird, entsprechend des vorherigen Timer-Beispiels, die wiederholte Ausführung eines Runnable gestartet. Die initiale Verzögerung beträgt wiederum 500 ms und das Intervall zwischen den Ausführungen 1 s. Der <code>ScheduledExecutorService</code> kann mittels der Methode <code>shutdown</code> beendet werden (Zeile 15).</li>
</ul> </ul>
<p>Das Interface <code>ScheduledExecutorService</code> erweitert das allgemeine funktionale Interface <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Executor.html"><code>Executor</code></a>, das lediglich die Methode <code>void execute(Runnable command)</code> spezifiziert, um ein Runnable nebenläufig auszuführen. Eine konkrete <code>Executor</code>- und <code>ScheduledExecutorService</code>-Implementierung ist die Klasse <code>ScheduledThreadPoolExecutor</code>, der im Konstruktor die Kapazität für einen <i>Thread-Pool</i> mitgegeben werden kann. Die Threads werden nun nicht mehr für je Runnable über einen Konstruktoraufruf <code>new Thread(...)</code> instantiiert, sondern aus einem begrenztem Thread-Pool wiederverwendet. Ein Thread-Pool bietet sich insbesondere für den typischen Anwendungsfall an, bei dem ein Server die Aufgaben (engl. <i>Tasks</i>) mehrerer Clients bedient. Das Thread-Pool-Muster vermeidet die ineffiziente Erzeugung eines neuen Threads je Aufgabe und schützt vor Ressourcenüberlastung, z.B. bei <i>Denial-of-Service</i>-Angriffen. Die Warteschlange, die zeitweise vor einem Thread-Pool entsteht, kann über unterschiedliche Strategien abgebaut werden, wobei <i>First-In-First-Out (FIFO)</i> häufig naheliegend und pragmatisch ist. Die folgende Abbildung verdeutlicht die Funktionsweise eines Thread-Pools.</p> <p>Das Interface <code>ScheduledExecutorService</code> erweitert das allgemeine funktionale Interface <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Executor.html"><code>Executor</code></a>, das lediglich die Methode <code>void execute(Runnable command)</code> spezifiziert, um ein Runnable nebenläufig auszuführen. Eine konkrete <code>Executor</code>- und <code>ScheduledExecutorService</code>-Implementierung ist die Klasse <code>ScheduledThreadPoolExecutor</code>, der im Konstruktor die Kapazität für einen <i>Thread-Pool</i> mitgegeben werden kann. Die Threads werden nun nicht mehr für jede Runnable über einen Konstruktoraufruf <code>new Thread(...)</code> instantiiert, sondern aus einem begrenztem Thread-Pool wiederverwendet. Ein Thread-Pool bietet sich insbesondere für den typischen Anwendungsfall an, bei dem ein Server die Aufgaben (engl. <i>Tasks</i>) mehrerer Clients bedient. Das Thread-Pool-Muster vermeidet die ineffiziente Erzeugung eines neuen Threads je Aufgabe und schützt vor Ressourcenüberlastung, z.B. bei <i>Denial-of-Service</i>-Angriffen. Die Warteschlange, die zeitweise vor einem Thread-Pool entsteht, kann über unterschiedliche Strategien abgebaut werden, wobei <i>First-In-First-Out (FIFO)</i> häufig naheliegend und pragmatisch ist. Die folgende Abbildung verdeutlicht die Funktionsweise eines Thread-Pools.</p>
<img src="media/concurrency_pool.png" style="width:360px"> <img src="media/concurrency_pool.png" style="width:360px">
<label>Funktionsweise eines Thread-Pools</label> <label>Funktionsweise eines Thread-Pools</label>
<p>In dem folgenden Code-Beispiel wird die begrenzte Kapazität eines Thread-Pools demonstriert. Es sollen 7 Runnables gestartet werden (Zeilen 18-19). Der Thread-Pool lässt aber nur die Ausführung von 3 nebenläufigen Threads zu (Zeile 1), so dass die letzten 4 Runnables warten müssen, bis wieder ein Thread freigegeben wird. Da die Runnables jeweils zufällig lange arbeiten (Zeilen 8-11), ist nicht vorhersehbar, in welchem der 3 Threads die letzten 4 Runnables ausgeführt werden. Eine mögliche Ausgabe könnte wie folgt aussehen:</p> <p>In dem folgenden Code-Beispiel wird die begrenzte Kapazität eines Thread-Pools demonstriert. Es sollen 7 Runnables gestartet werden (Zeilen 18-19). Der Thread-Pool lässt aber nur die Ausführung von 3 nebenläufigen Threads zu (Zeile 1), sodass die letzten 4 Runnables warten müssen, bis wieder ein Thread freigegeben wird. Da die Runnables jeweils zufällig lange arbeiten (Zeilen 8-11), ist nicht vorhersehbar, in welchem der 3 Threads die letzten 4 Runnables ausgeführt werden. Eine mögliche Ausgabe könnte wie folgt aussehen:</p>
<pre>Task 2 worked for 2522 ms in pool-1-thread-2. Total time: 2538 ms <pre>Task 2 worked for 2522 ms in pool-1-thread-2. Total time: 2538 ms
Task 3 worked for 2617 ms in pool-1-thread-3. Total time: 2633 ms Task 3 worked for 2617 ms in pool-1-thread-3. Total time: 2633 ms
Task 1 worked for 2916 ms in pool-1-thread-1. Total time: 2932 ms Task 1 worked for 2916 ms in pool-1-thread-1. Total time: 2932 ms
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment