Java 101: Grundlegendes zu Java-Threads, Teil 1: Einführung in Threads und ausführbare Dateien
Dieser Artikel ist der erste in einer vierteiligen Java 101- Reihe, in der Java-Threads untersucht werden. Obwohl Sie vielleicht denken, dass das Threading in Java schwierig zu verstehen ist, möchte ich Ihnen zeigen, dass Threads leicht zu verstehen sind. In diesem Artikel stelle ich Ihnen Java-Threads und Runnables vor. In den folgenden Artikeln werden Synchronisation (über Sperren), Synchronisationsprobleme (wie Deadlock), Warte- / Benachrichtigungsmechanismus, Zeitplanung (mit und ohne Priorität), Thread-Unterbrechung, Timer, Volatilität, Thread-Gruppen und lokale Thread-Variablen untersucht .
Beachten Sie, dass dieser Artikel (Teil des JavaWorld-Archivs) im Mai 2013 mit neuen Codelisten und herunterladbarem Quellcode aktualisiert wurde.
Grundlegendes zu Java-Threads - Lesen Sie die gesamte Serie
- Teil 1: Einführung in Threads und Runnables
- Teil 2: Synchronisation
- Teil 3: Thread-Planung und Warten / Benachrichtigen
- Teil 4: Fadengruppen und Volatilität
Was ist ein Thread?
Konzeptionell ist der Begriff eines Threads nicht schwer zu verstehen: Es handelt sich um einen unabhängigen Ausführungspfad durch den Programmcode. Wenn mehrere Threads ausgeführt werden, unterscheidet sich der Pfad eines Threads durch denselben Code normalerweise von den anderen. Angenommen, ein Thread führt das Bytecode-Äquivalent des if
Teils einer if-else-Anweisung aus , während ein anderer Thread das Bytecode-Äquivalent des else
Teils ausführt . Wie verfolgt die JVM die Ausführung jedes Threads? Die JVM gibt jedem Thread einen eigenen Methodenaufrufstapel. Zusätzlich zum Verfolgen des aktuellen Bytecode-Befehls verfolgt der Methodenaufrufstapel lokale Variablen, Parameter, die die JVM an eine Methode übergibt, und den Rückgabewert der Methode.
Wenn mehrere Threads Bytecode-Befehlssequenzen in demselben Programm ausführen, wird diese Aktion als Multithreading bezeichnet . Multithreading kommt einem Programm auf verschiedene Weise zugute:
- Programme mit Multithread-GUI (grafische Benutzeroberfläche) reagieren weiterhin auf Benutzer, während sie andere Aufgaben ausführen, z. B. das Repaginieren oder Drucken eines Dokuments.
- Threaded-Programme werden normalerweise schneller beendet als ihre Gegenstücke ohne Thread. Dies gilt insbesondere für Threads, die auf einem Multiprozessor-Computer ausgeführt werden, auf dem jeder Thread über einen eigenen Prozessor verfügt.
Java führt Multithreading durch seine java.lang.Thread
Klasse durch. Jedes Thread
Objekt beschreibt einen einzelnen Ausführungsthread. Diese Ausführung erfolgt in Thread
der run()
Methode von. Da die Standardmethode run()
nichts bewirkt, müssen Sie eine Unterklasse erstellen Thread
und überschreiben run()
, um nützliche Arbeit zu leisten. In Thread
Listing 1 finden Sie Informationen zu Threads und Multithreading im Kontext von :
Listing 1. ThreadDemo.java
// ThreadDemo.java class ThreadDemo { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); for (int i = 0; i < 50; i++) System.out.println ("i = " + i + ", i * i = " + i * i); } } class MyThread extends Thread { public void run () { for (int count = 1, row = 1; row < 20; row++, count++) { for (int i = 0; i < count; i++) System.out.print ('*'); System.out.print ('\n'); } } }
Listing 1 zeigt den Quellcode einer Anwendung, die aus Klassen ThreadDemo
und besteht MyThread
. Die Klasse ThreadDemo
steuert die Anwendung, indem sie ein MyThread
Objekt erstellt, einen Thread startet, der diesem Objekt zugeordnet ist, und Code zum Drucken einer Quadrattabelle ausführt. Im Gegensatz dazu MyThread
überschreibt Thread
‚s - run()
Verfahren (auf dem Standardausgabestrom) zu drucken , ein rechtwinkliges Dreieck , bestehend aus Stern - Zeichen.
Thread-Planung und die JVM
Die meisten (wenn nicht alle) JVM-Implementierungen verwenden die Threading-Funktionen der zugrunde liegenden Plattform. Da diese Funktionen plattformspezifisch sind, kann die Reihenfolge der Ausgabe Ihrer Multithread-Programme von der Reihenfolge der Ausgabe anderer Personen abweichen. Dieser Unterschied ergibt sich aus der Planung, ein Thema, das ich später in dieser Reihe untersuche.
Wenn Sie eingeben java ThreadDemo
, um die Anwendung auszuführen, erstellt die JVM einen Startthread für die Ausführung, der die main()
Methode ausführt . Durch Ausführen mt.start ();
weist der Startthread die JVM an, einen zweiten Ausführungsthread zu erstellen, der die Bytecode-Anweisungen ausführt, die MyThread
die run()
Methode des Objekts umfassen . Wenn die start()
Methode zurückkehrt, führt der Start-Thread seine for
Schleife aus, um eine Tabelle mit Quadraten zu drucken, während der neue Thread die run()
Methode zum Drucken des rechtwinkligen Dreiecks ausführt .
Wie sieht die Ausgabe aus? Lauf ThreadDemo
um es herauszufinden. Sie werden feststellen, dass die Ausgabe jedes Threads dazu neigt, sich mit der Ausgabe des anderen zu vermischen. Dies liegt daran, dass beide Threads ihre Ausgabe an denselben Standardausgabestream senden.
Die Thread-Klasse
Um Multithread-Code besser schreiben zu können, müssen Sie zunächst die verschiedenen Methoden der Thread
Klasse verstehen . In diesem Abschnitt werden viele dieser Methoden erläutert. Insbesondere lernen Sie Methoden zum Starten von Threads, zum Benennen von Threads, zum Deaktivieren von Threads, zum Bestimmen, ob ein Thread aktiv ist, zum Verbinden eines Threads mit einem anderen Thread und zum Auflisten aller aktiven Threads in der Threadgruppe und den Untergruppen des aktuellen Threads. Ich diskutiere auch Thread
die Debugging-Hilfsmittel und Benutzer-Threads im Vergleich zu Daemon-Threads.
Ich werde den Rest der Thread
Methoden in den folgenden Artikeln vorstellen , mit Ausnahme der veralteten Methoden von Sun.
Veraltete Methoden
Sun hat eine Vielzahl von Thread
Methoden wie suspend()
und abgelehnt resume()
, da sie Ihre Programme blockieren oder Objekte beschädigen können. Daher sollten Sie sie in Ihrem Code nicht aufrufen. In der SDK-Dokumentation finden Sie Problemumgehungen für diese Methoden. Ich gehe nicht auf veraltete Methoden in dieser Serie ein.
Fäden konstruieren
Thread
hat acht Konstruktoren. Die einfachsten sind:
Thread()
, wodurch einThread
Objekt mit einem Standardnamen erstellt wirdThread(String name)
, wodurch einThread
Objekt mit einem Namen erstellt wird, den dasname
Argument angibt
Die nächst einfacheren Konstruktoren sind Thread(Runnable target)
und Thread(Runnable target, String name)
. Abgesehen von den Runnable
Parametern sind diese Konstruktoren mit den oben genannten Konstruktoren identisch. Der Unterschied: Die Runnable
Parameter identifizieren Objekte außerhalb Thread
, die die run()
Methoden bereitstellen . (Sie lernen Runnable
später in diesem Artikel.) Die letzten vier Konstrukteuren ähneln Thread(String name)
, Thread(Runnable target)
und Thread(Runnable target, String name)
; Die endgültigen Konstruktoren enthalten jedoch auch ein ThreadGroup
Argument für organisatorische Zwecke.
Einer der letzten vier Konstruktoren Thread(ThreadGroup group, Runnable target, String name, long stackSize)
ist insofern interessant, als Sie die gewünschte Größe des Methodenaufrufstapels des Threads angeben können. Die Angabe dieser Größe erweist sich in Programmen mit Methoden, die Rekursion verwenden - eine Ausführungstechnik, bei der sich eine Methode wiederholt selbst aufruft - als hilfreich, um bestimmte Probleme elegant zu lösen. Durch explizites Festlegen der Stapelgröße können Sie manchmal StackOverflowError
s verhindern . Eine zu große Größe kann jedoch zu OutOfMemoryError
s führen. Außerdem betrachtet Sun die Größe des Methodenaufrufstapels als plattformabhängig. Je nach Plattform kann sich die Größe des Methodenaufrufstapels ändern. Denken Sie daher sorgfältig über die Auswirkungen auf Ihr Programm nach, bevor Sie den aufrufenden Code schreiben Thread(ThreadGroup group, Runnable target, String name, long stackSize)
.
Starten Sie Ihre Fahrzeuge
Threads ähneln Fahrzeugen: Sie bewegen Programme von Anfang bis Ende. Thread
und Thread
Unterklassenobjekte sind keine Threads. Stattdessen beschreiben sie die Attribute eines Threads, z. B. seinen Namen, und enthalten Code (über eine run()
Methode), den der Thread ausführt. Wenn ein neuer Thread ausgeführt werden muss run()
, ruft ein anderer Thread Thread
die start()
Methode des Objekts oder seines Unterklassenobjekts auf . Um beispielsweise einen zweiten Thread zu starten, wird der main()
Startthread der Anwendung, der ausgeführt wird, aufgerufen start()
. Als Reaktion darauf arbeitet der Thread-Handling-Code der JVM mit der Plattform zusammen, um sicherzustellen, dass der Thread Thread
die run()
Methode eines Objekts oder seines Unterklassenobjekts ordnungsgemäß initialisiert und aufruft .
Nach start()
Abschluss werden mehrere Threads ausgeführt. Da wir dazu neigen, linear zu denken, fällt es uns oft schwer, die gleichzeitige (gleichzeitige) Aktivität zu verstehen, die auftritt, wenn zwei oder mehr Threads ausgeführt werden. Daher sollten Sie ein Diagramm untersuchen, das zeigt, wo ein Thread (seine Position) im Verhältnis zur Zeit ausgeführt wird. Die folgende Abbildung zeigt eine solche Tabelle.

Die Grafik zeigt mehrere wichtige Zeiträume:
- Die Initialisierung des Startthreads
- Der Moment, in dem der Thread ausgeführt wird
main()
- Der Moment, in dem der Thread ausgeführt wird
start()
- Der Moment
start()
erstellt einen neuen Thread und kehrt zu zurückmain()
- Die Initialisierung des neuen Threads
- In dem Moment, in dem der neue Thread ausgeführt wird
run()
- Die verschiedenen Momente, in denen jeder Thread endet
Note that the new thread's initialization, its execution of run()
, and its termination happen simultaneously with the starting thread's execution. Also note that after a thread calls start()
, subsequent calls to that method before the run()
method exits cause start()
to throw a java.lang.IllegalThreadStateException
object.
What's in a name?
During a debugging session, distinguishing one thread from another in a user-friendly fashion proves helpful. To differentiate among threads, Java associates a name with a thread. That name defaults to Thread
, a hyphen character, and a zero-based integer number. You can accept Java's default thread names or you can choose your own. To accommodate custom names, Thread
provides constructors that take name
arguments and a setName(String name)
method. Thread
also provides a getName()
method that returns the current name. Listing 2 demonstrates how to establish a custom name via the Thread(String name)
constructor and retrieve the current name in the run()
method by calling getName()
:
Listing 2. NameThatThread.java
// NameThatThread.java class NameThatThread { public static void main (String [] args) { MyThread mt; if (args.length == 0) mt = new MyThread (); else mt = new MyThread (args [0]); mt.start (); } } class MyThread extends Thread { MyThread () { // The compiler creates the byte code equivalent of super (); } MyThread (String name) { super (name); // Pass name to Thread superclass } public void run () { System.out.println ("My name is: " + getName ()); } }
You can pass an optional name argument to MyThread
on the command line. For example, java NameThatThread X
establishes X
as the thread's name. If you fail to specify a name, you'll see the following output:
My name is: Thread-1
If you prefer, you can change the super (name);
call in the MyThread (String name)
constructor to a call to setName (String name)
—as in setName (name);
. That latter method call achieves the same objective—establishing the thread's name—as super (name);
. I leave that as an exercise for you.
Naming main
Java assigns the name main
to the thread that runs the main()
method, the starting thread. You typically see that name in the Exception in thread "main"
message that the JVM's default exception handler prints when the starting thread throws an exception object.
To sleep or not to sleep
Later in this column, I will introduce you to animation— repeatedly drawing on one surface images that slightly differ from each other to achieve a movement illusion. To accomplish animation, a thread must pause during its display of two consecutive images. Calling Thread
's static sleep(long millis)
method forces a thread to pause for millis
milliseconds. Another thread could possibly interrupt the sleeping thread. If that happens, the sleeping thread awakes and throws an InterruptedException
object from the sleep(long millis)
method. As a result, code that calls sleep(long millis)
must appear within a try
block—or the code's method must include InterruptedException
in its throws
clause.
Zur Demonstration sleep(long millis)
habe ich eine CalcPI1
Bewerbung geschrieben. Diese Anwendung startet einen neuen Thread, der einen mathematischen Algorithmus verwendet, um den Wert der mathematischen Konstante pi zu berechnen. Während der Berechnung des neuen Threads wird der Start-Thread durch Aufrufen für 10 Millisekunden angehalten sleep(long millis)
. Nachdem der Startthread aktiviert wurde, wird der pi-Wert gedruckt, den der neue Thread in einer Variablen speichert pi
. Listing 3 präsentiert CalcPI1
den Quellcode:
Listing 3. CalcPI1.java
// CalcPI1.java class CalcPI1 { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); try { Thread.sleep (10); // Sleep for 10 milliseconds } catch (InterruptedException e) { } System.out.println ("pi = " + mt.pi); } } class MyThread extends Thread { boolean negative = true; double pi; // Initializes to 0.0, by default public void run () { for (int i = 3; i < 100000; i += 2) { if (negative) pi -= (1.0 / i); else pi += (1.0 / i); negative = !negative; } pi += 1.0; pi *= 4.0; System.out.println ("Finished calculating PI"); } }
Wenn Sie dieses Programm ausführen, wird eine ähnliche (aber wahrscheinlich nicht identische) Ausgabe wie folgt angezeigt:
pi = -0.2146197014017295 Finished calculating PI