Vorwort. Onkel Petja
Nehmen wir also an, wir wollten eine Flasche Wasser füllen. Eine Flasche und Leitungswasser von Onkel Petja stehen zur Verfügung. Onkel Petja ließ heute einen neuen Wasserhahn installieren und lobte immer wieder seine Schönheit. Davor benutzte er nur einen alten, verstopften Zapfhahn, sodass die Warteschlangen bei der Abfüllung riesig waren. Nachdem ich ein wenig herumgefummelt hatte, war das Geräusch des Wassereinfüllens aus der Richtung des Verschüttens zu hören, nach 2 Minuten ist die Flasche immer noch in der Füllphase, die übliche Schlange hat sich hinter uns gebildet und das Bild in meinem Kopf zeigt, wie fürsorglich Onkel ist Petya wählt nur die besten H2O-Moleküle für unsere Flasche aus. Der vom Leben geschulte Onkel Petja beruhigt die besonders Aggressiven und verspricht, so schnell wie möglich fertig zu werden. Nachdem er mit der Flasche fertig ist, nimmt er die nächste und stellt den üblichen Druck auf, der jedoch nicht alle Möglichkeiten des neuen Wasserhahns offenbart. Die Leute sind nicht glücklich...
Theorie
Multithreading ist die Fähigkeit einer Plattform, mehrere Threads innerhalb eines einzigen Prozesses zu erstellen. Das Erstellen und Ausführen eines Threads ist viel einfacher als das Erstellen eines Prozesses. Wenn also mehrere parallele Aktionen in einem Programm implementiert werden müssen, werden zusätzliche Threads verwendet. In der JVM wird jedes Programm im Hauptthread ausgeführt und der Rest wird von dort aus gestartet. Innerhalb desselben Prozesses können Threads Daten untereinander austauschen. Wenn Sie einen neuen Thread starten, können Sie ihn mithilfe der Methode als Benutzerthread deklarieren
setDaemon(true);
Solche Threads werden automatisch beendet, wenn keine anderen laufenden Threads mehr vorhanden sind. Threads haben Arbeitspriorität (die Wahl der Priorität garantiert nicht, dass der Thread mit der höchsten Priorität schneller abgeschlossen wird als der Thread mit der niedrigeren Priorität).
- MIN_PRIORITÄT
- NORM_PRIORITY (Standard)
- MAX_PRIORITY
Grundlegende Methoden beim Arbeiten mit Streams:
run()
– führt den Thread aus
start()
– startet einen Thread
getName()
– gibt den Thread-Namen zurück
setName()
– gibt den Namen des Streams an
wait()
notify()
– geerbte Methode, der Thread wartet darauf, dass die Methode von einem anderen Thread aufgerufen wird
notify()
– geerbte Methode, setzt einen zuvor gestoppten Thread fort
notifyAll()
– geerbte Methode, setzt zuvor gestoppte Threads fort
sleep()
– Pausiert den Stream für eine bestimmte Zeit
join()
– wartet darauf, dass der Thread abgeschlossen ist
interrupt()
– unterbricht die Thread-Ausführung
Weitere Methoden finden Sie
hier. Es ist Zeit, über neue Threads nachzudenken, wenn Ihr Programm über Folgendes verfügt:
- Netzwerkzugang
- Zugriff auf das Dateisystem
- GUI
Thread-Klasse
Threads werden in Java als Klasse
Thread
und ihre Nachkommen dargestellt. Das folgende Beispiel ist eine einfache Implementierung der Stream-Klasse.
import static java.lang.System.out;
public class ExampleThread extends Thread{
public static void main(String[] args) {
out.println("Основной поток");
new ExampleThread().start();
}
@Override
public void run() {
out.println("Новый поток");
}
}
Als Ergebnis erhalten wir
Основной поток
Новый поток
Hier erstellen wir unsere Klasse und machen sie zu einem Nachkommen der Klasse
Thread
. Anschließend schreiben wir die Methode main(), um den Hauptthread zu starten und die
run()
Klassenmethode zu überschreiben
Thread
. Nachdem wir nun eine Instanz unserer Klasse erstellt und ihre geerbte Methode ausgeführt haben,
start()
starten wir einen neuen Thread, in dem alles ausgeführt wird, was im Hauptteil der Methode beschrieben ist
run()
. Es klingt kompliziert, aber wenn man sich den Beispielcode ansieht, sollte alles klar sein.
Ausführbare Schnittstelle
Oracle schlägt außerdem vor, die Schnittstelle zu implementieren, um einen neuen Thread zu starten
Runnable
, was uns mehr Designflexibilität bietet als die einzige verfügbare Vererbung im vorherigen Beispiel (wenn Sie sich die Quelle der Klasse ansehen,
Thread
können Sie sehen, dass sie auch die Schnittstelle implementiert
Runnable
). Lassen Sie uns die empfohlene Methode zum Erstellen eines neuen Threads verwenden.
import static java.lang.System.out;
public class ExampleRunnable implements Runnable {
public static void main(String[] args) {
out.println("Основной поток");
new Thread(new ExampleRunnable()).start();
}
@Override
public void run() {
out.println("Новый поток");
}
}
Als Ergebnis erhalten wir
Основной поток
Новый поток
Die Beispiele sind sehr ähnlich, weil Beim Schreiben des Codes mussten wir eine abstrakte Methode implementieren, die
run()
in der Schnittstelle beschrieben ist
Runnable
. Das Starten eines neuen Threads ist etwas anders. Wir haben eine Instanz der Klasse erstellt
Thread
, indem wir einen Verweis auf eine Instanz unserer Schnittstellenimplementierung als Parameter übergeben haben
Runnable
. Mit diesem Ansatz können Sie neue Threads erstellen, ohne die Klasse direkt zu erben
Thread
.
Lange Operationen
Das folgende Beispiel zeigt deutlich die Vorteile der Verwendung mehrerer Threads. Nehmen wir an, wir haben eine einfache Aufgabe, die mehrere langwierige Berechnungen erfordert. Vor diesem Artikel hätten wir sie in einer Methode gelöst und sie zur
main()
leichteren Wahrnehmung vielleicht in separate Methoden, vielleicht sogar in Klassen, zerlegt, aber das Wesentliche wäre dasselbe. Alle Operationen würden sequentiell nacheinander ausgeführt. Lassen Sie uns umfangreiche Berechnungen simulieren und deren Ausführungszeit messen.
public class ComputeClass {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 1");
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 2");
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 3");
long timeSpent = System.currentTimeMillis() - startTime;
System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
}
}
Als Ergebnis erhalten wir
complete 1
complete 2
complete 3
программа выполнялась 9885 миллисекунд
Die Ausführungszeit lässt zu wünschen übrig, und die ganze Zeit sehen wir auf einen leeren Ausgabebildschirm, und die Situation ist der Geschichte über Onkel Petja sehr ähnlich, nur haben wir, die Entwickler, seine Rolle jetzt nicht ausgenutzt alle Möglichkeiten moderner Geräte. Wir werden uns verbessern.
public class ComputeClass {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
new MyThread(1).start();
new MyThread(2).start();
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 3");
long timeSpent = System.currentTimeMillis() - startTime;
System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
}
}
class MyThread extends Thread{
int n;
MyThread(int n){
this.n = n;
}
@Override
public void run() {
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete " + n);
}
}
Als Ergebnis erhalten wir
complete 1
complete 2
complete 3
программа выполнялась 3466 миллисекунд
Die Laufzeit wurde erheblich verkürzt (dieser Effekt wird möglicherweise nicht erreicht oder verlängert möglicherweise sogar die Ausführungszeit auf Prozessoren, die kein Multithreading unterstützen). Es ist zu beachten, dass Threads möglicherweise nicht in der richtigen Reihenfolge enden. Wenn der Entwickler Vorhersehbarkeit von Aktionen benötigt, muss er diese für einen bestimmten Fall unabhängig implementieren.
Thread-Gruppen
Threads können in Java zu Gruppen zusammengefasst werden, hierfür wird die Klasse verwendet
ThreadGroup
. Gruppen können sowohl einzelne Threads als auch ganze Gruppen umfassen. Dies kann praktisch sein, wenn Sie beispielsweise mit dem Netzwerk verbundene Flüsse unterbrechen müssen, wenn die Verbindung unterbrochen wird.
Mehr über Gruppen können Sie hier lesen. Ich hoffe, dass Ihnen das Thema jetzt klarer geworden ist und Ihre Benutzer zufrieden sein werden.
GO TO FULL VERSION