CodeGym /Java-Blog /Random-DE /Besser zusammen: Java und die Thread-Klasse. Teil I – Thr...
John Squirrels
Level 41
San Francisco

Besser zusammen: Java und die Thread-Klasse. Teil I – Threads zur Ausführung

Veröffentlicht in der Gruppe Random-DE

Einführung

Multithreading war von Anfang an in Java integriert. Schauen wir uns also kurz das sogenannte Multithreading an. Besser zusammen: Java und die Thread-Klasse.  Teil I – Threads zur Ausführung – 1Als Bezugspunkt nehmen wir die offizielle Lektion von Oracle: „ Lektion: Die „Hello World!“-Anwendung “. Wir werden den Code unseres Hello World-Programms wie folgt leicht ändern:

class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argsist ein Array von Eingabeparametern, die beim Start des Programms übergeben werden. Speichern Sie diesen Code in einer Datei mit einem Namen, der dem Klassennamen entspricht und die Erweiterung hat .java. Kompilieren Sie es mit dem Dienstprogramm javacjavac HelloWorldApp.java : . Dann führen wir unseren Code mit einem Parameter aus, zum Beispiel „Roger“: java HelloWorldApp Roger Besser zusammen: Java und die Thread-Klasse.  Teil I – Threads zur Ausführung – 2Unser Code weist derzeit einen schwerwiegenden Fehler auf. Wenn Sie kein Argument übergeben (also nur „java HelloWorldApp“ ausführen), erhalten wir eine Fehlermeldung:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
Im Thread namens „main“ ist eine Ausnahme (dh ein Fehler) aufgetreten. Java hat also Threads? Hier beginnt unsere Reise.

Java und Threads

Um zu verstehen, was ein Thread ist, müssen Sie verstehen, wie ein Java-Programm startet. Ändern wir unseren Code wie folgt:

class HelloWorldApp {
    public static void main(String[] args) {
		while (true) { 
			// Do nothing
		}
	}
}
Jetzt kompilieren wir es noch einmal mit javac. Der Einfachheit halber führen wir unseren Java-Code in einem separaten Fenster aus. Unter Windows kann dies folgendermaßen erfolgen: start java HelloWorldApp. Jetzt verwenden wir das Dienstprogramm jps , um zu sehen, welche Informationen Java uns mitteilen kann: Besser zusammen: Java und die Thread-Klasse.  Teil I – Threads zur Ausführung – 3Die erste Zahl ist die PID oder Prozess-ID. Was ist ein Prozess?

A process is a combination of code and data sharing a common virtual address space.
Bei Prozessen werden verschiedene Programme bei ihrer Ausführung voneinander isoliert: Jede Anwendung nutzt ihren eigenen Bereich im Speicher, ohne andere Programme zu beeinträchtigen. Um mehr zu erfahren, empfehle ich die Lektüre dieses Tutorials: Prozesse und Threads . Ein Prozess kann nicht ohne Thread existieren. Wenn also ein Prozess existiert, dann hat er mindestens einen Thread. Aber wie kommt das in Java zustande? Wenn wir ein Java-Programm starten, beginnt die Ausführung mit der mainMethode. Es ist, als würden wir in das Programm einsteigen, daher mainwird diese spezielle Methode als Einstiegspunkt bezeichnet. Die mainMethode muss immer „public static void“ sein, damit die Java Virtual Machine (JVM) mit der Ausführung unseres Programms beginnen kann. Weitere Informationen: Warum ist die Java-Hauptmethode statisch?. Es stellt sich heraus, dass der Java-Launcher (java.exe oder javaw.exe) eine einfache C-Anwendung ist: Er lädt die verschiedenen DLLs, aus denen die JVM tatsächlich besteht. Der Java-Launcher führt einen bestimmten Satz von JNI-Aufrufen (Java Native Interface) durch. JNI ist ein Mechanismus zur Verbindung der Welt der Java Virtual Machine mit der Welt von C++. Der Launcher ist also nicht die JVM selbst, sondern ein Mechanismus zum Laden. Es kennt die richtigen Befehle, die zum Starten der JVM ausgeführt werden müssen. Es weiß, wie man JNI-Aufrufe verwendet, um die erforderliche Umgebung einzurichten. Zum Einrichten dieser Umgebung gehört natürlich auch das Erstellen des Hauptthreads, der „main“ heißt. Um besser zu veranschaulichen, welche Threads in einem Java-Prozess vorhanden sind, verwenden wir jvisualvmTool, das im JDK enthalten ist. Wenn wir die PID eines Prozesses kennen, können wir sofort Informationen über diesen Prozess sehen: jvisualvm --openpid <process id> Besser zusammen: Java und die Thread-Klasse.  Teil I – Threads zur Ausführung – 4Interessanterweise hat jeder Thread seinen eigenen separaten Bereich im Speicher, der dem Prozess zugewiesen ist. Diese Speicherstruktur wird Stapel genannt. Ein Stapel besteht aus Frames. Ein Frame stellt die Aktivierung einer Methode dar (einen unvollendeten Methodenaufruf). Ein Frame kann auch als StackTraceElement dargestellt werden (siehe Java API für StackTraceElement ). Weitere Informationen zum jedem Thread zugewiesenen Speicher finden Sie in der Diskussion hier: „ Wie weist Java (JVM) jedem Thread einen Stapel zu ?“. Wenn Sie sich die Java-API ansehen und nach dem Wort „Thread“ suchen, finden Sie java.lang.ThreadKlasse. Dies ist die Klasse, die einen Thread in Java darstellt, und wir müssen damit arbeiten. Besser zusammen: Java und die Thread-Klasse.  Teil I – Ausführungsthreads – 5

java.lang.Thread

In Java wird ein Thread durch eine Instanz der java.lang.ThreadKlasse dargestellt. Sie sollten sofort verstehen, dass Instanzen der Thread-Klasse selbst keine Ausführungsthreads sind. Dies ist lediglich eine Art API für die Low-Level-Threads, die von der JVM und dem Betriebssystem verwaltet werden. Wenn wir die JVM mit dem Java-Launcher starten, erstellt sie einen mainThread namens „main“ und einige andere Verwaltungsthreads. Wie im JavaDoc für die Thread-Klasse angegeben: When a Java Virtual Machine starts up, there is usually a single non-daemon thread. Es gibt zwei Arten von Threads: Daemons und Nicht-Daemons. Daemon-Threads sind Hintergrund-(Housekeeping-)Threads, die einige Arbeiten im Hintergrund ausführen. Das Wort „Dämon“ bezieht sich auf Maxwells Dämon. Mehr erfahren Sie in diesem Wikipedia-Artikel . Wie in der Dokumentation angegeben, führt die JVM das Programm (den Prozess) so lange aus, bis:
  • Die Methode Runtime.exit() wird aufgerufen
  • Alle NICHT-Daemon-Threads beenden ihre Arbeit (ohne Fehler oder mit ausgelösten Ausnahmen).
Daraus folgt ein wichtiges Detail: Daemon-Threads können jederzeit beendet werden. Daher gibt es keine Garantien für die Integrität ihrer Daten. Dementsprechend eignen sich Daemon-Threads für bestimmte Housekeeping-Aufgaben. Java verfügt beispielsweise über einen Thread, der für die Verarbeitung von finalize()Methodenaufrufen verantwortlich ist, also Threads, die am Garbage Collector (gc) beteiligt sind. Jeder Thread ist Teil einer Gruppe ( ThreadGroup ). Und Gruppen können Teil anderer Gruppen sein und eine bestimmte Hierarchie oder Struktur bilden.

public static void main(String[] args) {
	Thread currentThread = Thread.currentThread();
	ThreadGroup threadGroup = currentThread.getThreadGroup();
	System.out.println("Thread: " + currentThread.getName());
	System.out.println("Thread Group: " + threadGroup.getName());
	System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
Gruppen bringen Ordnung in die Threadverwaltung. Zusätzlich zu Gruppen verfügen Threads über einen eigenen Ausnahmehandler. Schauen Sie sich ein Beispiel an:

public static void main(String[] args) {
	Thread th = Thread.currentThread();
	th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
		@Override
		public void uncaughtException(Thread t, Throwable e) {
			System.out.println("An error occurred: " + e.getMessage());
		}
	});
    System.out.println(2/0);
}
Eine Division durch Null führt zu einem Fehler, der vom Handler abgefangen wird. Wenn Sie keinen eigenen Handler angeben, ruft die JVM den Standardhandler auf, der den Stack-Trace der Ausnahme an StdError ausgibt. Jeder Thread hat auch eine Priorität. Weitere Informationen zu Prioritäten finden Sie in diesem Artikel: Java Thread Priority in Multithreading .

Einen Thread erstellen

Wie in der Dokumentation angegeben, haben wir zwei Möglichkeiten, einen Thread zu erstellen. Die erste Möglichkeit besteht darin, eine eigene Unterklasse zu erstellen. Zum Beispiel:

public class HelloWorld{
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello, World!");  
        }
    }
    
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}
Wie Sie sehen, erfolgt die Arbeit der Aufgabe in der run()Methode, der Thread selbst wird jedoch in der start()Methode gestartet. Verwechseln Sie diese Methoden nicht: Wenn wir die r- un()Methode direkt aufrufen, wird kein neuer Thread gestartet. Es ist die start()Methode, die die JVM auffordert, einen neuen Thread zu erstellen. Diese Option, bei der wir Thread erben, ist bereits insofern schlecht, als wir Thread in unsere Klassenhierarchie aufnehmen. Der zweite Nachteil besteht darin, dass wir beginnen, gegen das Prinzip der „Alleinverantwortung“ zu verstoßen. Das heißt, unsere Klasse ist gleichzeitig für die Steuerung des Threads und für die Ausführung einer Aufgabe in diesem Thread verantwortlich. Was ist der richtige Weg? Die Antwort findet sich in derselben run()Methode, die wir überschreiben:

public void run() {
	if (target != null) {
		target.run();
	}
}
Hier targetsind einige java.lang.Runnable, die wir beim Erstellen einer Instanz der Thread-Klasse übergeben können. Das bedeutet, dass wir Folgendes tun können:

public class HelloWorld{
    public static void main(String[] args) {
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("Hello, World!");
            } 
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Runnableist seit Java 1.8 auch eine funktionale Schnittstelle. Dadurch ist es möglich, noch schöneren Code für die Aufgabe eines Threads zu schreiben:

public static void main(String[] args) {
	Runnable task = () -> { 
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

Abschluss

Ich hoffe, diese Diskussion verdeutlicht, was ein Thread ist, wie Threads entstehen und welche grundlegenden Operationen mit Threads ausgeführt werden können. Im nächsten Teil werden wir versuchen zu verstehen, wie Threads miteinander interagieren, und den Thread-Lebenszyklus untersuchen. Besser zusammen: Java und die Thread-Klasse. Teil II – Synchronisierung Gemeinsam besser: Java und die Thread-Klasse. Teil III – Gemeinsam besser interagieren: Java und die Thread-Klasse. Teil IV – Callable, Future und Freunde Gemeinsam besser: Java und die Thread-Klasse. Teil V – Executor, ThreadPool, Fork/Join Gemeinsam besser: Java und die Thread-Klasse. Teil VI – Feuer los!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION