Durch Multithreading gelöste Probleme
Multithreading wurde eigentlich erfunden, um zwei wichtige Ziele zu erreichen:-
Machen Sie mehrere Dinge gleichzeitig.
Im obigen Beispiel führten verschiedene Threads (Familienmitglieder) mehrere Aktionen parallel aus: Sie spülten Geschirr, gingen in den Laden und packten Sachen.
Wir können ein Beispiel anbieten, das näher an der Programmierung liegt. Angenommen, Sie haben ein Programm mit einer Benutzeroberfläche. Wenn Sie im Programm auf „Weiter“ klicken, sollten einige Berechnungen durchgeführt werden und der Benutzer sollte den folgenden Bildschirm sehen. Wenn diese Aktionen nacheinander ausgeführt würden, würde das Programm einfach hängen bleiben, nachdem der Benutzer auf die Schaltfläche „Weiter“ geklickt hat. Dem Benutzer wird der Bildschirm mit der Schaltfläche „Weiter“ angezeigt, bis das Programm alle internen Berechnungen durchführt und den Teil erreicht, in dem die Benutzeroberfläche aktualisiert wird.
Nun, ich schätze, wir werden ein paar Minuten warten!
Oder wir könnten unser Programm überarbeiten oder, wie Programmierer sagen, „parallelisieren“. Lassen Sie uns unsere Berechnungen in einem Thread durchführen und die Benutzeroberfläche in einem anderen zeichnen. Die meisten Computer verfügen über genügend Ressourcen, um dies zu tun. Wenn wir diesen Weg wählen, friert das Programm nicht ein und der Benutzer kann reibungslos zwischen den Bildschirmen wechseln, ohne sich Gedanken darüber machen zu müssen, was im Inneren passiert. Das eine stört das andere nicht :)
-
Führen Sie Berechnungen schneller durch.
Hier ist alles viel einfacher. Wenn unser Prozessor über mehrere Kerne verfügt, was bei den meisten Prozessoren heutzutage der Fall ist, können mehrere Kerne unsere Aufgabenliste parallel bearbeiten. Wenn wir 1000 Aufgaben ausführen müssen und jede eine Sekunde dauert, kann ein Kern die Liste natürlich in 1000 Sekunden abschließen, zwei Kerne in 500 Sekunden, drei in etwas mehr als 333 Sekunden usw.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("I'm Thread! My name is " + getName());
}
}
Um Threads zu erstellen und auszuführen, müssen wir eine Klasse erstellen und dafür sorgen, dass sie java.lang erbt . Thread- Klasse und überschreiben Sie deren run()- Methode. Diese letzte Anforderung ist sehr wichtig. In der run()- Methode definieren wir die Logik für die Ausführung unseres Threads. Wenn wir nun eine Instanz von MyFirstThread erstellen und ausführen, zeigt die Methode run() eine Zeile mit einem Namen an: Die Methode getName() zeigt den Systemnamen des Threads an, der automatisch zugewiesen wird. Aber warum sprechen wir zögernd? Lassen Sie uns eines erstellen und es herausfinden!
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Konsolenausgabe:
Ich bin Thread! Mein Name ist Thread-2,
ich bin Thread! Mein Name ist Thread-1,
ich bin Thread! Mein Name ist Thread-0,
ich bin Thread! Mein Name ist Thread-3,
ich bin Thread! Mein Name ist Thread-6,
ich bin Thread! Mein Name ist Thread-7,
ich bin Thread! Mein Name ist Thread-4,
ich bin Thread! Mein Name ist Thread-5,
ich bin Thread! Mein Name ist Thread-9,
ich bin Thread! Mein Name ist Thread-8.
Lassen Sie uns 10 Threads ( MyFirstThread- Objekte, die Thread erben) erstellen und sie starten, indem wir für jedes Objekt die Methode start() aufrufen . Nach dem Aufruf der start()- Methode wird die Logik in der run()- Methode ausgeführt. Hinweis: Die Threadnamen sind nicht in der richtigen Reihenfolge. Es ist seltsam, dass sie nicht nacheinander waren:, Thread-1 , Thread-2 und so weiter? Tatsächlich ist dies ein Beispiel für eine Zeit, in der „sequenzielles“ Denken nicht passt. Das Problem besteht darin, dass wir nur Befehle zum Erstellen und Ausführen von 10 Threads bereitgestellt haben. Der Thread-Scheduler, ein spezieller Mechanismus des Betriebssystems, entscheidet über deren Ausführungsreihenfolge. Sein präzises Design und seine Entscheidungsstrategie sind Themen für eine ausführliche Diskussion, auf die wir uns jetzt nicht näher einlassen. Das Wichtigste ist, dass der Programmierer die Ausführungsreihenfolge von Threads nicht kontrollieren kann. Um den Ernst der Lage zu verstehen, versuchen Sie, die main()-Methode im obigen Beispiel noch ein paar Mal auszuführen.
Konsolenausgabe beim zweiten Durchlauf:
Ich bin Thread! Mein Name ist Thread-0, ich bin Thread!
Mein Name ist Thread-4, ich bin Thread!
Mein Name ist Thread-3, ich bin Thread!
Mein Name ist Thread-2, ich bin Thread!
Mein Name ist Thread-1, ich bin Thread!
Mein Name ist Thread-5, ich bin Thread!
Mein Name ist Thread-6, ich bin Thread!
Mein Name ist Thread-8, ich bin Thread!
Mein Name ist Thread-9, ich bin Thread!
Mein Name ist Thread-7
Konsolenausgabe aus dem dritten Durchlauf:
Ich bin Thread! Mein Name ist Thread-0,
ich bin Thread! Mein Name ist Thread-3,
ich bin Thread! Mein Name ist Thread-1,
ich bin Thread! Mein Name ist Thread-2,
ich bin Thread! Mein Name ist Thread-6,
ich bin Thread! Mein Name ist Thread-4,
ich bin Thread! Mein Name ist Thread-9,
ich bin Thread! Mein Name ist Thread-5,
ich bin Thread! Mein Name ist Thread-7,
ich bin Thread! Mein Name ist Thread-8
Durch Multithreading verursachte Probleme
In unserem Beispiel mit Büchern haben Sie gesehen, dass Multithreading sehr wichtige Aufgaben löst und unsere Programme schneller machen kann. Oft um ein Vielfaches schneller. Doch Multithreading gilt als schwieriges Thema. Tatsächlich führt es bei unsachgemäßer Anwendung zu Problemen, anstatt sie zu lösen. Wenn ich sage, dass „Probleme entstehen“, dann meine ich das nicht im abstrakten Sinne. Es gibt zwei spezifische Probleme, die Multithreading verursachen kann: Deadlock und Race Conditions. Deadlock ist eine Situation, in der mehrere Threads auf voneinander gehaltene Ressourcen warten und keiner von ihnen weiter ausgeführt werden kann. Wir werden in den folgenden Lektionen mehr darüber sprechen. Das folgende Beispiel reicht zunächst aus:
- Thread-1 beendet die Interaktion mit Objekt-1 und wechselt zu Objekt-2, sobald Thread-2 die Interaktion mit Objekt-2 beendet und zu Objekt-1 wechselt.
- Thread-2 beendet die Interaktion mit Objekt-2 und wechselt zu Objekt-1, sobald Thread-1 die Interaktion mit Objekt-1 beendet und zu Objekt-2 wechselt.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Stellen Sie sich nun vor, dass das Programm dafür verantwortlich ist, einen Roboter zu betreiben, der Essen kocht!
Thread-0 holt Eier aus dem Kühlschrank.
Thread-1 schaltet den Herd ein.
Thread-2 holt eine Pfanne und stellt sie auf den Herd.
Thread-3 zündet den Herd an.
Thread-4 gießt Öl in die Pfanne.
Thread-5 zerbricht die Eier und schüttet sie in die Pfanne.
Thread-6 wirft die Eierschalen in den Mülleimer.
Thread-7 nimmt die gekochten Eier vom Brenner.
Thread-8 legt die gekochten Eier auf einen Teller.
Thread-9 spült das Geschirr.
Sehen Sie sich die Ergebnisse unseres Programms an:
Thread ausgeführt: Thread-0
Thread ausgeführt: Thread-2
Thread ausgeführt Thread-1
Thread ausgeführt: Thread-4
Thread ausgeführt: Thread-9
Thread ausgeführt: Thread-5
Thread ausgeführt: Thread-8
Thread ausgeführt: Thread-7
Thread ausgeführt: Thread-3
Ist das eine Comedy-Routine? :) Und das alles, weil die Arbeit unseres Programms von der Ausführungsreihenfolge der Threads abhängt. Bei der kleinsten Verletzung der vorgeschriebenen Reihenfolge verwandelt sich unsere Küche in eine Hölle und ein verrückter Roboter zerstört alles um sie herum. Dies ist auch ein häufiges Problem bei der Multithread-Programmierung. Davon wird man mehr als einmal hören. Zum Abschluss dieser Lektion möchte ich ein Buch über Multithreading empfehlen. 
GO TO FULL VERSION