Einführung in Java-Threads

Dieser Artikel, einer der ersten, der von JavaWorld veröffentlicht wurde, beschreibt, wie Threads in der Programmiersprache Java implementiert werden, beginnend mit einem allgemeinen Überblick über Threads.

Einfach ausgedrückt ist ein Thread der Ausführungspfad eines Programms. Die meisten heute geschriebenen Programme werden als einzelner Thread ausgeführt und verursachen Probleme, wenn mehrere Ereignisse oder Aktionen gleichzeitig auftreten müssen. Angenommen, ein Programm kann beim Lesen von Tastenanschlägen keine Bilder zeichnen. Das Programm muss sich voll und ganz auf die Tastatureingabe konzentrieren, da nicht mehr als ein Ereignis gleichzeitig verarbeitet werden kann. Die ideale Lösung für dieses Problem ist die nahtlose Ausführung von zwei oder mehr Abschnitten eines Programms gleichzeitig. Threads ermöglicht es uns, dies zu tun.

Erfahren Sie mehr über Java-Threads

Dieser Artikel ist Teil des JavaWorld-Archivs für technische Inhalte. Weitere Informationen zu Java-Threads und zur Parallelität finden Sie im Folgenden:

Grundlegendes zu Java-Threads ( Java 101- Serie, 2002):

  • Teil 1: Einführung in Threads und Runnables
  • Teil 2: Thread-Synchronisation
  • Teil 3: Thread-Planung und Warten / Benachrichtigen
  • Teil 4: Fadengruppen und Volatilität

Zum Thema passende Artikel

  • Hyper-Threaded Java: Verwenden der Java Concurrency API (2006)
  • Bessere Monitore für Multithread-Programme (2007)
  • Grundlegendes zur Parallelität von Akteuren, Teil 1 (2009)
  • Erkennung und Handhabung von Hängefäden (2011)

Überprüfen Sie auch die Javaworld -Sitemap und Suchmaschinen .

Multithread-Anwendungen bieten ihre leistungsstarke Leistung, indem sie viele Threads gleichzeitig in einem einzigen Programm ausführen. Aus logischer Sicht bedeutet Multithreading, dass mehrere Zeilen eines einzelnen Programms gleichzeitig ausgeführt werden können. Es ist jedoch nicht dasselbe, ein Programm zweimal zu starten und zu sagen, dass mehrere Zeilen eines Programms gleichzeitig ausgeführt werden Zeit. In diesem Fall behandelt das Betriebssystem die Programme als zwei separate und unterschiedliche Prozesse. Unter Unix wird durch das Verzweigen eines Prozesses ein untergeordneter Prozess mit einem anderen Adressraum für Code und Daten erstellt. Jedoch,fork()verursacht viel Overhead für das Betriebssystem, was es zu einem sehr CPU-intensiven Betrieb macht. Wenn Sie stattdessen einen Thread starten, wird ein effizienter Ausführungspfad erstellt, während der ursprüngliche Datenbereich vom übergeordneten Thread freigegeben wird. Die Idee, den Datenbereich gemeinsam zu nutzen, ist sehr nützlich, wirft jedoch einige Problembereiche auf, die wir später erörtern werden.

Threads erstellen

Die Entwickler von Java haben freundlicherweise zwei Möglichkeiten zum Erstellen von Threads entwickelt: Implementieren einer Schnittstelle und Erweitern einer Klasse. Durch das Erweitern einer Klasse erbt Java Methoden und Variablen von einer übergeordneten Klasse. In diesem Fall kann nur eine einzelne übergeordnete Klasse erweitert oder geerbt werden. Diese Einschränkung in Java kann durch die Implementierung von Schnittstellen überwunden werden. Dies ist die häufigste Methode zum Erstellen von Threads. (Beachten Sie, dass durch das Erben die Klasse lediglich als Thread ausgeführt werden kann. Die start()Ausführung usw. muss von der Klasse durchgeführt werden.)

Schnittstellen bieten Programmierern die Möglichkeit, die Grundlagen für eine Klasse zu legen. Sie werden verwendet, um die Anforderungen für eine Reihe von zu implementierenden Klassen zu entwerfen. Die Schnittstelle richtet alles ein und die Klasse oder Klassen, die die Schnittstelle implementieren, erledigen die ganze Arbeit. Die verschiedenen Klassen, die die Schnittstelle implementieren, müssen denselben Regeln folgen.

Es gibt einige Unterschiede zwischen einer Klasse und einer Schnittstelle. Erstens kann eine Schnittstelle nur abstrakte Methoden und / oder statische Endvariablen (Konstanten) enthalten. Klassen hingegen können Methoden implementieren und Variablen enthalten, die keine Konstanten sind. Zweitens kann eine Schnittstelle keine Methoden implementieren. Eine Klasse, die eine Schnittstelle implementiert, muss alle in dieser Schnittstelle definierten Methoden implementieren. Eine Schnittstelle kann sich von anderen Schnittstellen aus erstrecken und (im Gegensatz zu Klassen) von mehreren Schnittstellen aus. Darüber hinaus kann eine Schnittstelle mit dem neuen Operator nicht instanziiert werden. ist zum Beispiel Runnable a=new Runnable();nicht erlaubt.

Die erste Methode zum Erstellen eines Threads besteht darin, einfach aus der ThreadKlasse zu erweitern. Tun Sie dies nur, wenn die Klasse, die Sie als Thread ausführen müssen, niemals von einer anderen Klasse erweitert werden muss. Die ThreadKlasse ist im Paket java.lang definiert, das importiert werden muss, damit unsere Klassen ihre Definition kennen.

import java.lang.*; public class Counter extends Thread { public void run() { .... } }

Im obigen Beispiel wird eine neue Klasse erstellt Counter, die die ThreadKlasse erweitert und die Thread.run()Methode für ihre eigene Implementierung überschreibt . Bei dieser run()Methode wird die gesamte Arbeit des CounterKlassenthreads erledigt. Dieselbe Klasse kann durch Implementierung von Runnable erstellt werden:

import java.lang.*; public class Counter implements Runnable { Thread T; public void run() { .... } }

Hier wird die abstrakte run()Methode in der Runnable-Schnittstelle definiert und implementiert. Beachten Sie, dass wir eine Instanz der ThreadKlasse als Variable der CounterKlasse haben. Der einzige Unterschied zwischen den beiden Methoden besteht darin, dass durch die Implementierung von Runnable die Erstellung der Klasse flexibler wird Counter. Im obigen Beispiel besteht weiterhin die Möglichkeit, die CounterKlasse bei Bedarf zu erweitern. Die Mehrheit der erstellten Klassen, die als Thread ausgeführt werden müssen, implementiert Runnable, da sie wahrscheinlich einige andere Funktionen einer anderen Klasse erweitern.

Denken Sie nicht, dass die Runnable-Schnittstelle echte Arbeit leistet, wenn der Thread ausgeführt wird. Es ist lediglich eine Klasse, die erstellt wurde, um eine Vorstellung vom Design der ThreadKlasse zu vermitteln. Tatsächlich ist es sehr klein und enthält nur eine abstrakte Methode. Hier ist die Definition der Runnable-Schnittstelle direkt aus der Java-Quelle:

package java.lang; public interface Runnable { public abstract void run(); }

Das ist alles, was es zur Runnable-Schnittstelle gibt. Eine Schnittstelle bietet nur einen Entwurf, auf dem Klassen implementiert werden sollen. Im Fall der Runnable-Schnittstelle wird nur die Definition der run()Methode erzwungen . Daher wird der größte Teil der Arbeit in der ThreadKlasse erledigt . Ein genauerer Blick auf einen Abschnitt in der Definition der ThreadKlasse gibt eine Vorstellung davon, was wirklich vor sich geht:

public class Thread implements Runnable { ... public void run() { if (target != null) { target.run(); } } ... }

Aus dem obigen Codeausschnitt geht hervor, dass die Thread-Klasse auch die Runnable-Schnittstelle implementiert. Thread. run()Überprüft, ob die Zielklasse (die Klasse, die als Thread ausgeführt werden soll) ungleich Null ist, und führt dann die run()Methode des Ziels aus. In diesem run()Fall wird die Methode des Ziels als eigener Thread ausgeführt.

Starten und Stoppen

Da die verschiedenen Möglichkeiten zum Erstellen einer Instanz eines Threads jetzt offensichtlich sind, werden wir die Implementierung von Threads diskutieren, beginnend mit den verfügbaren Möglichkeiten zum Starten und Stoppen von Threads mithilfe eines kleinen Applets, das einen Thread enthält, um die Mechanik zu veranschaulichen:

CounterThread-Beispiel und Quellcode

The above applet will start counting from 0 displaying its output to both the screen and the console. A quick glance might give the impression that the program will start counting and display every number, but this is not the case. A closer examination of the execution of this applet will reveal its true identity.

In this case, the CounterThread class was forced to implement Runnable since it extended the class Applet. As in all applets, the init() method gets executed first. In init(), the variable Count is initialized to zero and a new instance of the Thread class is created. By passing this to the Thread constructor, the new thread will know which object to run. In this case this is a reference to CounterThread. After the thread is created it needs to be started. The call to start() will call the target's run() method, which is CounterThread.run(). The call to start() will return right away and the thread will start executing at the same time. Note that the run() method is an infinite loop. It is infinite because once the run() method exits, the thread stops executing. The run() method will increment the variable Count, sleep for 10 milliseconds and send a request to refresh the applet's display.

Note that it is important to sleep somewhere in a thread. If not, the thread will consume all CPU time for the process and will not allow any other methods such as threads to be executed. Another way to cease the execution of a thread is to call the stop() method. In this example, the thread stops when the mouse is pressed while the cursor is in the applet. Depending on the speed of the computer the applet runs on, not every number will be displayed, because the incrementing is done independent of the painting of the applet. The applet can not be refreshed at every request, so the OS will queue the requests and successive refresh requests will be satisfied with one refresh. While the refreshes are queuing up, the Count is still being incremented but not displayed.

Suspending and resuming

Once a thread is stopped, it cannot be restarted with the start() command, since stop() will terminate the execution of a thread. Instead you can pause the execution of a thread with the sleep() method. The thread will sleep for a certain period of time and then begin executing when the time limit is reached. But, this is not ideal if the thread needs to be started when a certain event occurs. In this case, the suspend() method allows a thread to temporarily cease executing and the resume() method allows the suspended thread to start again. The following applet shows the above example modified to suspend and resume the applet.

public class CounterThread2 extends Applet implements Runnable { Thread t; int Count; boolean suspended; public boolean mouseDown(Event e,int x, int y) { if(suspended) t.resume(); else t.suspend(); suspended = !suspended; return true; } ... }

CounterThread2 Example and Source code

Um den aktuellen Status des Applets zu verfolgen, wird die boolesche Variable suspendedverwendet. Das Unterscheiden der verschiedenen Zustände eines Applets ist wichtig, da einige Methoden Ausnahmen auslösen, wenn sie im falschen Zustand aufgerufen werden. Wenn das Applet beispielsweise gestartet und gestoppt wurde, wird beim Ausführen der start()Methode eine IllegalThreadStateExceptionAusnahme ausgelöst.