-
Notifications
You must be signed in to change notification settings - Fork 0
/
multithreading.html
18 lines (17 loc) · 15.1 KB
/
multithreading.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html><html lang="de-ch"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Threads und Runnables - Finecloud</title><meta name="description" content="In Java ist ein Thread zunächst ein Objekt wie jedes andere auch. Ein Thread kann erzeugt werden, indem man einen Konstruktoren der Klasse Thread aufruft. Diesen kann man behandeln wie jedes andere Objekt auch, ihn in einer Variabel speichern, als Parameter übergeben usw. Erst wenn…"><meta name="generator" content="Publii Open-Source CMS for Static Site"><link rel="stylesheet" href="https://www.finecloud.ch/media/plugins/syntaxHighlighter/prism-black.css"><link rel="canonical" href="https://www.finecloud.ch/multithreading.html"><link rel="alternate" type="application/atom+xml" href="https://www.finecloud.ch/feed.xml"><link rel="alternate" type="application/json" href="https://www.finecloud.ch/feed.json"><meta property="og:title" content="Threads und Runnables"><meta property="og:site_name" content="Finecloud"><meta property="og:description" content="In Java ist ein Thread zunächst ein Objekt wie jedes andere auch. Ein Thread kann erzeugt werden, indem man einen Konstruktoren der Klasse Thread aufruft. Diesen kann man behandeln wie jedes andere Objekt auch, ihn in einer Variabel speichern, als Parameter übergeben usw. Erst wenn…"><meta property="og:url" content="https://www.finecloud.ch/multithreading.html"><meta property="og:type" content="article"><link rel="shortcut icon" href="https://www.finecloud.ch/media/website/finecloud.png" type="image/png"><link rel="stylesheet" href="https://www.finecloud.ch/assets/css/style.css?v=39da73365516a098a9b73b721fc970e2"><script type="application/ld+json">{"@context":"http://schema.org","@type":"Article","mainEntityOfPage":{"@type":"WebPage","@id":"https://www.finecloud.ch/multithreading.html"},"headline":"Threads und Runnables","datePublished":"2022-06-17T06:32","dateModified":"2022-06-20T07:09","description":"In Java ist ein Thread zunächst ein Objekt wie jedes andere auch. Ein Thread kann erzeugt werden, indem man einen Konstruktoren der Klasse Thread aufruft. Diesen kann man behandeln wie jedes andere Objekt auch, ihn in einer Variabel speichern, als Parameter übergeben usw. Erst wenn…","author":{"@type":"Person","name":"Finecloud","url":"https://www.finecloud.ch/authors/finecloud/"},"publisher":{"@type":"Organization","name":"Finecloud"}}</script><meta name="google-site-verification" content="seFY9U12uiEq5U3_MyZiX6XWzk0AVFl9zITr2ZKsytY"></head><body><div class="site-container"><header class="top" id="js-header"><a class="logo" href="https://www.finecloud.ch/">Finecloud</a><nav class="navbar js-navbar"><button class="navbar__toggle js-toggle" aria-label="Menu" aria-haspopup="true" aria-expanded="false"><span class="navbar__toggle-box"><span class="navbar__toggle-inner">Menu</span></span></button><ul class="navbar__menu"><li><a href="https://www.finecloud.ch/" target="_self">Blog</a></li><li><a href="https://www.finecloud.ch/tags/" target="_self">Tags</a></li></ul></nav><div class="search"><div class="search__overlay js-search-overlay"><div class="search__overlay-inner"><form action="https://www.finecloud.ch/search.html" class="search__form"><input class="search__input js-search-input" type="search" name="q" placeholder="search..." aria-label="search..." autofocus="autofocus"></form><button class="search__close js-search-close" aria-label="Close">Close</button></div></div><button class="search__btn js-search-btn" aria-label="Search"><svg role="presentation" focusable="false"><use xlink:href="https://www.finecloud.ch/assets/svg/svg-map.svg#search"/></svg></button></div></header><main><article class="post"><div class="hero"><figure class="hero__image hero__image--overlay"><img src="https://www.finecloud.ch/media/website/download.jpg" srcset="https://www.finecloud.ch/media/website/responsive/download-xs.jpg 300w, https://www.finecloud.ch/media/website/responsive/download-sm.jpg 480w, https://www.finecloud.ch/media/website/responsive/download-md.jpg 768w, https://www.finecloud.ch/media/website/responsive/download-lg.jpg 1024w, https://www.finecloud.ch/media/website/responsive/download-xl.jpg 1360w, https://www.finecloud.ch/media/website/responsive/download-2xl.jpg 1600w" sizes="100vw" loading="eager" alt=""></figure><header class="hero__content"><div class="wrapper"><div class="post__meta"><time datetime="2022-06-17T06:32">Juni 17, 2022</time></div><h1>Threads und Runnables</h1></div></header></div><div class="wrapper post__entry"><p>In Java ist ein Thread zunächst ein Objekt wie jedes andere auch. Ein Thread kann erzeugt werden, indem man einen Konstruktoren der Klasse Thread aufruft. Diesen kann man behandeln wie jedes andere Objekt auch, ihn in einer Variabel speichern, als Parameter übergeben usw. Erst wenn man die Methode start ruft, kehrt dieser sofort zurück und der Thread, in dem der Aufruf erfolgt, wird mit der nächsten Anweisung fortgesetzt. Gleichzeitig und unabhängig wird nun aber auch der neue Thread ausgeführt und folgt seiner eigenen Anweisungsfolge.</p><p>Was ein Thead tun soll, übergibt man ihm normalerweise als Konstruktor, in Form eines Runnable-Objekts. Runnable ist ein funktionales Interface, das die Methode public void run () fordert. Man kann das Interface traditionell implementieren oder einen neuen Thea als Lambda übergeben:</p><p><code>public static void main(String[] args) throws Exception {</code><br><code> for (int i = 0; i < 10; i++){</code><br><code> Thread t = new Thread(() -> </code><br><code> IntStream.range(0, 10)</code><br><code> .forEach(j -></code><br><code> System.out.println(</code><br><code> Thread.currentThread().getName() + " Durchlauf " + j)),</code><br><code> "Thread " + i);</code><br><code> t.start();</code><br><code> }</code><br><code>}</code></p><p>Welcher Thread zuerst bis Zehn gezählt hat ist völlig willkürlich und bei der nächsten Ausführung bestimmt wieder anders. Es gibt keine Garantie welcher Thread zuerst ausgeführt wird. Man kann Einfluss darauf nehmen, wie viel Ausführungszeit einem Thread zugeteilt wird, indem man mit <em>setPriority</em> seine Priorität setzt. Ein Thread mit höherer Priorität erhält mehr Prozessorzeit als ein Thread mit niedriger Priorität. Aber auch <em>Thread.MAX_PRITORIY</em> zu setzen gibt keine Sicherheit, dass dieser Thread zuerst ausgeführt wird, es bedeutet lediglich, dass ihm insgesamt mehr Zeit zugeteilt wird. Das ist ein Problem der parallelen Programmierung: Man hat niemals die Sicherheit, dass Operationen in verschiedenen Threads in einer bestimmten Reihenfolge ausgeführt werden.</p><p>Der Lebenszyklus eines Threads sieht wie folgt aus:</p><ul><li>Wurde das Thread-Objekt erzeugt, aber noch nicht gestartet, so existiert der Thread noch nicht. Hier ist es wichtig die Begriffe klar zu trennen: Natürlich existiert das Java-Objekt vom Typ Thread. Es gibt aber zu diesem Zeitpunkt noch keinen weiteren Ausführungsstrang.</li><li>Sobald <em>start</em> gerufen wird, ist der Thread lebendig. Ein weiterer Ausführungsstrang wurde gestartet und ihm wird Rechenzeit zugeteilt. Ob ein Thread lebendig ist, können Sie mit der Methode <em>isAlive</em> prüfen.</li><li>Wenn das Ende der <em>run</em>-Methode des übergebeben Runnable erreicht ist, der Thread als keinen weiteren Code mehr auszuführen hat, ist der Thread tot. Ein toter Thread bleibt auch tot, man kann ihn nicht mit <em>start</em> erneut ausführen.</li></ul><p><code>public static class NetzEmpfaenger implements Runnable {</code><br><code> @Override</code><br><code> public void run() {</code><br><code> try (Socket verbindung = new Socket("…", 23456)){</code><br><code> InputStream in = verbindung.getInputStream();</code><br><code> byte[] buffer = new byte[256];</code><br><code> while (true) {</code><br><code> int gelesen = in.read(buffer);</code><br><code> verarbeite(buffer, gelesen);</code><br><code> }</code><br><code> } catch (IOException ex) {</code><br><code> verbindungVerloren();</code><br><code> }</code><br><code> }</code><br><code> protected void verarbeite(byte[] buffer, int gelesen){…}</code><br><code> protected void verbindungVerloren(){…}</code><br><code>}</code></p><p>Dieser Code liest solange Daten aus einer Netzwerkverbindung, bis die Verbindung unterbrochen wird. Währenddessen muss das Programm aber nie auf Daten aus dem Netzwerk warten, das blockierende Lesen passiert im Thread des Runnable und behindert keine anderen Threads. Solche laufende Threads haben den Nachteil das die JVM am laufen bleibt, bis alle Threads beendet wurden. Der Haupt-Thread, also der, der die main-Methode ausführt, ist in dieser Beziehung nichts besonderes. Auch wenn er beendet wurde, läuft die JVM so lange weiter, bis alle Threads tot sind. Mit solchen Threads wie dem oben gezeigten würde sie also nie beendet. Um die unsterbliche JVM zu vermeiden, könnte man entweder einen Mechanismus einbauen. um Ihre Threads zu stoppen - das ist aber umständlich -, oder man macht aus dem ausführenden Thread einen Daemon-Thread:</p><p><code>Thread t = new Thread(new NetzEmpfaenger(), "Netzwerkthread");</code><br><code>t.setDaemon(true);</code><br><code>t.start;</code></p><p>Werden in einem Programm nur noch Daemons ausgeführt, so kann die JVM beendet werden und die Daemon-Threads laufen weiter.</p><h3>Geteilte Ressourcen</h3><p>Herausfordernd wird es, wenn mehrere Threads auf dieselbe Ressource zugreifen möchten, zum Beispiel auf die gleiche Variable:</p><p><code>public class ThreadTest {</code><br><code> public int counter = 0;</code><br><code> public void run() {</code><br><code> Thread[] threads = new Thread[10];</code><br><code> for (int i = 0; i < 10; i++) {</code><br><code> threads[i] = new Thread(() -> {</code><br><code> for (int j = 0; j < 100; j++){</code><br><code> counter++;</code><br><code> }</code><br><code> }, "Thread " + i);</code><br><code> threads[i].start();</code><br><code> }</code><br><code> for (Thread t : threads) {</code><br><code> while (t.isAlive()) {</code><br><code> try {</code><br><code> t.join();</code><br><code> } catch (InterruptedException ex) {</code><br><code> }</code><br><code> }</code><br><code> }</code><br><code> System.out.println(counter);</code><br><code> }</code><br><code>}</code></p><p>Es werden zehr Threads gestartet. Jeder Thread führt nun eine Schleife mit 100 Durchläufen aus und addiert für jeden Durchlauf 1 zu counter, einem Feld der ThreadTest-Klasse.</p><p>Jeder erzeugte Thread-Objekt wird in einem Array gespeichert, weil es noch in einer zweiten Schleife verwendet wird. Dort wird mit der Methode join darauf gewartet, dass jeder der zehn Threads auch beendeet wird. Das ganze Drumherum, die Schleife und das Try-catch-Statement sind nur deshalb nötig, weil join theoretisch beim Warten unterbrochen werden könnte und dann eine InterruptedException werfen würde. Die untere Schleife dient nur dazu, auf die zehn Threads zu warten und erst, wenn sie alle beendet sind, den finalen Wert von counter auszugeben.</p><p>Das Problem in diesem Code ist, dass anstatt dem Erwarteten Resultat von 1000 teilweise das Ergebniss 850 oder andere Werte zurück kommen. Was passiert da?</p><p>Die Kurzschreibweise counter++ sieht in Wirklichkeit eher so aus:</p><p>int neuerWert = counter + 1;<br>counter = neuerWert;</p><p>Der Wert von Counter wird ausgelesen, dann wird eins addiert und dieser neue Wert nach counter zurückgegeben. Das ist kein Problem, solange es nur einen Thread gibt. Aber mit mehreren Threads kann das passieren:</p><ol><li>Thread 1 lies den Wert von counter aus und erhält zum Beispiel 17.</li><li>Thread 2 liest den Wert von counter aus und erhält ebenfalls 17.</li><li>Thread 2 addiert 1 zu seinem gelesenen Wert und schreibt das Resultat 18 nach counter.</li><li>Thread 1 addiert 1 zu seinem gelesenen Wert, erhält ebenfalls das Ergebnis 18 und schreibt dieses nach counter.</li></ol><p>Das Problem ist, dass der Inkrement-Operator nicht <em>atomar</em> ist. Als atomar bezeichnet werden solche Operatoren, die nicht von einem anderen Thread unterbrochen werden können.</p><p>Operationen, die ihrerseits ohne weiteres zutun atomar sind, gibt es in Java nur eine: die Zuweisung. Objektvariablen und primitive Variabel mit der Ausnahme von long und double werden neue Werte von der JVM in nur einem Arbeitsschritt zugewiesen, es ist für einen anderen Thread schlicht nicht möglich, eine Zuweisung zu unterbrechen.</p><p>Atomare Zuweisungen lösen das obrige Problem aber leider nicht. Um den Zähler trotz Zugriff aus mehreren Threads korrekt zu halten, müssen wir selbst dafür sorgen, dass die Variable atomar inkrementiert wird. Dazu gibt es zwei Wege: einen einfacheen mit begrenzen Möglichkeiten mit "<a href="https://www.finecloud.ch/atomare-datentypen.html">Atomaren Datentypen</a>" oder einen komplexen, aber vielseitigen mittels "Synchronisation".</p><p> </p><p> </p></div><footer class="wrapper post__footer"><p class="post__last-updated">This article was updated on Juni 20, 2022</p><ul class="post__tag"><li><a href="https://www.finecloud.ch/tags/java/">java</a></li><li><a href="https://www.finecloud.ch/tags/lambda/">lambda</a></li><li><a href="https://www.finecloud.ch/tags/parallel/">parallel</a></li><li><a href="https://www.finecloud.ch/tags/softwareentwicklung/">software development</a></li></ul><div class="post__share"></div></footer></article></main><footer class="footer"><div class="footer__copyright"><p>Powered by Publii</p></div><button onclick="backToTopFunction()" id="backToTop" class="footer__bttop" aria-label="Back to top" title="Back to top"><svg><use xlink:href="https://www.finecloud.ch/assets/svg/svg-map.svg#toparrow"/></svg></button></footer></div><script>window.publiiThemeMenuConfig = {
mobileMenuMode: 'sidebar',
animationSpeed: 300,
submenuWidth: 'auto',
doubleClickTime: 500,
mobileMenuExpandableSubmenus: true,
relatedContainerForOverlayMenuSelector: '.top',
};</script><script defer="defer" src="https://www.finecloud.ch/assets/js/scripts.min.js?v=6ca8b60e6534a3888de1205e82df8528"></script><script>var images = document.querySelectorAll('img[loading]');
for (var i = 0; i < images.length; i++) {
if (images[i].complete) {
images[i].classList.add('is-loaded');
} else {
images[i].addEventListener('load', function () {
this.classList.add('is-loaded');
}, false);
}
}</script><script defer="defer" src="https://www.finecloud.ch/media/plugins/syntaxHighlighter/prism.js"></script></body></html>