Heim  >  Artikel  >  Java  >  Detaillierte Analyse von Multithread-Programmiermethoden in JAVA (mit Beispielen)

Detaillierte Analyse von Multithread-Programmiermethoden in JAVA (mit Beispielen)

王林
王林nach vorne
2019-08-30 11:46:251982Durchsuche

1. Programm, Prozess, Thread

Ein Programm ist ein geordneter Satz von Anweisungen, der im Volksmund auch als mehrere Zeilen von verstanden werden kann Code. Es hat keine Bedeutung für die Ausführung. Es kann sich um eine einfache Textdatei handeln oder um eine ausführbare Datei, die nach der Kompilierung generiert wird.
 Im engeren Sinne ist ein Prozess eine Instanz eines laufenden Programms, im weiteren Sinne ist ein Prozess eine laufende Aktivität eines Programms mit bestimmten unabhängigen Funktionen für eine bestimmte Datensammlung. Der Prozess ist die Grundeinheit der Ressourcenzuweisung durch das Betriebssystem.
Ein Thread ist die kleinste Einheit in einem Prozess, die unabhängig ausgeführt werden kann. Er ist auch die Grundeinheit für die unabhängige Planung und den Versand durch den Prozessor. Ein Prozess kann mehrere Threads enthalten, jeder Thread führt seine eigenen Aufgaben aus und alle Threads im selben Prozess teilen sich Ressourcen im Prozess, wie z. B. Speicherplatz, Dateihandles usw.

2. Einführung in die Multithread-Programmierung

1. Was ist Multithread-Programmierung

Mehr Thread-Programmiertechnologie ist ein wichtiges Merkmal der Java-Sprache. Der Sinn der Multithread-Programmierung besteht darin, Programmaufgaben in mehrere parallele Unteraufgaben aufzuteilen und diese Unteraufgaben mehreren Threads zur Ausführung zuzuweisen.
 Multithread-Programmierung ist ein Programmierparadigma mit Threads als grundlegender abstrakter Einheit. Allerdings ist die Multithread-Programmierung nicht nur so einfach wie die Verwendung mehrerer Threads zum Programmieren, sondern bringt auch eigene Probleme mit sich, die gelöst werden müssen. Multithread-Programmierung und objektorientierte Programmierung sind kompatibel, das heißt, wir können Multithread-Programmierung basierend auf objektorientierter Programmierung implementieren. Tatsächlich ist ein Thread in der Java-Plattform ein Objekt.

2. Warum Multithread-Programmierung verwenden

Heutige Computer verfügen oft über Multiprozessorkerne und jeder Thread kann nur auf einem Prozessor gleichzeitig ausgeführt werden. Vorgesetzter. Wenn nur ein einzelner Thread für die Entwicklung verwendet wird, können die Ressourcen des Mehrkernprozessors nicht vollständig genutzt werden, um die Ausführungseffizienz des Programms zu verbessern. Beim Einsatz von Multithreading zur Programmierung können unterschiedliche Threads auf unterschiedlichen Prozessoren laufen. Auf diese Weise wird nicht nur die Auslastung der Computerressourcen erheblich verbessert, sondern auch die Ausführungseffizienz des Programms verbessert.

3. Einführung in die JAVA-Thread-API

Die Klasse java.lang.Thread ist die Thread-Implementierung der Java-Plattform. Eine Instanz der Thread-Klasse oder ihrer Unterklasse ist ein Thread.

1. Erstellen, Starten und Ausführen von Threads

In der Java-Plattform ist das Erstellen eines Threads ein Beispiel für das Erstellen einer Thread-Klasse (oder ihrer Unterklasse). Jeder Thread hat eine Aufgabe auszuführen. Die Aufgabenverarbeitungslogik des Threads kann direkt in der Ausführungsmethode der Thread-Klasse implementiert oder über diese Methode aufgerufen werden. Daher entspricht die Ausführungsmethode der Eingabemethode der Aufgabenverarbeitungslogik des Threads. Sie sollte direkt von Java Virtual aufgerufen werden Maschine beim Ausführen des entsprechenden Threads. Es sollte nicht vom Anwendungscode aufgerufen werden.
Durch das Ausführen eines Threads kann die Java Virtual Machine tatsächlich die Ausführungsmethode des Threads ausführen, sodass der Logikcode für die Aufgabenverarbeitung ausgeführt werden kann. Wenn ein Thread nicht gestartet wird, wird seine Ausführungsmethode niemals ausgeführt. Dazu müssen Sie zunächst den Thread starten. Die Startmethode der Thread-Klasse wird verwendet, um den entsprechenden Thread zu starten. Das Wesentliche beim Starten eines Threads besteht darin, die virtuelle Maschine aufzufordern, den entsprechenden Thread auszuführen. Wann dieser Thread ausgeführt werden kann, wird vom Thread-Scheduler bestimmt (der Thread-Scheduler ist Teil des Betriebssystems). Daher bedeutet der Aufruf der Startmethode eines Threads nicht, dass die Ausführung des Threads sofort begonnen hat, möglicherweise später ausgeführt wird oder möglicherweise nie ausgeführt wird.
Im Folgenden werden zwei Möglichkeiten zum Erstellen von Threads vorgestellt (eigentlich gibt es noch andere Möglichkeiten, die in den folgenden Artikeln ausführlich vorgestellt werden). Schauen wir uns vorher den Quellcode der run-Methode der Thread-Klasse an:

// Code 1-1@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

Diese Ausführungsmethode ist in der Schnittstelle Runnable definiert. Sie akzeptiert keine Parameter und hat keinen Rückgabewert. Tatsächlich gibt es in der Runnable-Schnittstelle nur eine Methode, daher ist diese Schnittstelle eine funktionale Schnittstelle, was bedeutet, dass wir Lambda-Ausdrücke dort verwenden können, wo Runnable benötigt wird. Die Thread-Klasse implementiert diese Schnittstelle und muss daher diese Methode implementieren. Ziel ist ein Feld in der Thread-Klasse und sein Typ ist ebenfalls Runnable. Das Zielfeld gibt an, was dieser Thread ausführen muss, und die Ausführungsmethode der Thread-Klasse führt nur die Ausführungsmethode des Ziels aus.
Wir haben gerade erwähnt, dass die Java Virtual Machine automatisch die Ausführungsmethode des Threads aufruft. Allerdings wurde die Ausführungsmethode der Thread-Klasse definiert und wir haben keine Möglichkeit, den Code, den wir ausführen müssen, in die Ausführungsmethode der Thread-Klasse einzufügen. Daher können wir andere Möglichkeiten in Betracht ziehen, das Verhalten der Ausführungsmethode zu beeinflussen. Die erste besteht darin, die Thread-Klasse zu erben und die Ausführungsmethode zu überschreiben, sodass die JVM beim Ausführen des Threads unsere überschriebene Ausführungsmethode anstelle der Ausführungsmethode der Thread-Klasse aufruft to Thread Die Zielmethode der Klasse, und die Thread-Klasse verfügt zufällig über mehrere Konstruktoren, die dem Ziel direkt Werte zuweisen können. Auf diese Weise führt die JVM weiterhin den Code aus, den wir beim Aufruf der Ausführungsmethode übergeben haben.
 In der Java-Plattform kann jeder Thread seinen eigenen Standardnamen haben, wenn wir eine Instanz der Thread-Klasse erstellen. Dieser Name erleichtert uns die Unterscheidung verschiedener Threads.
 Der folgende Code verwendet die beiden oben genannten Methoden, um zwei Threads zu erstellen. Die Aufgabe, die sie ausführen müssen, ist sehr einfach: Drucken Sie eine Zeile mit einer Willkommensnachricht und fügen Sie ihre eigenen Namen ein.

public class WelcomeApp {
    public static void main(String[] args) {
        Thread thread1 = new WelcomeThread();
        Thread thread2 = new Thread(() -> System.out.println("2. Welcome, I'm " + Thread.currentThread().getName()));
        thread1.start();
        thread2.start();
    }
}class WelcomeThread extends Thread {
    @Override
    public void run() {
        System.out.println("1. Welcome, I'm " + Thread.currentThread().getName());
    }
}

Das Folgende ist die Ausgabe dieses Programms, wenn es ausgeführt wird:

1. Welcome, I'm Thread-0
2. Welcome, I'm Thread-1

Führen Sie dieses Programm mehrmals aus. Wir können feststellen, dass die Ausgabe dieses Programms auch sein kann:

2. Welcome, I'm Thread-1
1. Welcome, I'm Thread-0

Dies zeigt, dass Thread1 zwar vor Thread2 gestartet wird, dies jedoch nicht bedeutet, dass Thread1 vor Thread2 ausgeführt wird.
Egal welche Methode zum Erstellen eines Threads verwendet wird, sobald die Ausführung der Ausführungsmethode des Threads (von der JVM aufgerufen) endet, endet auch die Ausführung des entsprechenden Threads. Natürlich umfasst das Ausführungsende der Ausführungsmethode das normale Ende (die Ausführungsmethode kehrt normal zurück) und die Beendigung, die durch im Code ausgelöste Ausnahmen verursacht wird. Die vom Thread nach Abschluss seiner Ausführung belegten Ressourcen (z. B. Speicherplatz) werden von der JVM wie andere Java-Objekte recycelt.
Threads sind „Einwegartikel“. Wir können einen Thread, der seine Ausführung beendet hat, nicht erneut ausführen, indem wir die Startmethode erneut aufrufen. Tatsächlich kann die Startmethode nur einmal aufgerufen werden. Wenn Sie die Startmethode derselben Thread-Instanz mehrmals aufrufen, wird eine IllegalThreadStateException-Ausnahme ausgelöst.

2. Thread-Attribute

Thread-Attribute umfassen Thread-Nummer, Name, Kategorie und Priorität. Details werden in der folgenden Tabelle angezeigt:

Detaillierte Analyse von Multithread-Programmiermethoden in JAVA (mit Beispielen)

Die Konzepte von Daemon-Threads und Benutzer-Threads sind oben erwähnt. Je nachdem, ob der Thread verhindert, dass die Java Virtual Machine normal stoppt, können wir die Threads in Java in Daemon-Threads (Daemon Thread) und Benutzer-Threads (User Thread, auch Nicht-Daemon-Threads genannt) unterteilen. Das Daemon-Attribut des Threads wird verwendet, um anzuzeigen, ob der entsprechende Thread ein Daemon-Thread ist. Benutzer-Threads verhindern, dass die Java Virtual Machine normal stoppt. Das heißt, eine Java Virtual Machine kann nur dann normal stoppen, wenn alle ihre Benutzer-Threads ausgeführt wurden (d. h. der Thread.run()-Aufruf wurde nicht beendet). Der Daemon-Thread hat keinen Einfluss auf den normalen Stopp der Java Virtual Machine. Auch wenn der Daemon-Thread in der Anwendung ausgeführt wird, hat er keinen Einfluss auf den normalen Stopp der Java Virtual Machine. Daher werden Daemon-Threads normalerweise zur Ausführung unwichtiger Aufgaben verwendet, z. B. zur Überwachung des Ausführungsstatus anderer Threads.
Wenn die Java Virtual Machine zum Stoppen gezwungen wird, beispielsweise durch die Verwendung des Kill-Befehls zum erzwungenen Beenden eines Java Virtual Machine-Prozesses unter dem Linux-System, kann natürlich nicht einmal der Benutzerthread das Stoppen der Java Virtual Machine verhindern.

3. Allgemeine Methoden der Thread-Klasse

Detaillierte Analyse von Multithread-Programmiermethoden in JAVA (mit Beispielen)

Jeder Code in Java wird immer in einem bestimmten Thread ausgeführt. Der Thread, der den aktuellen Code ausführt, wird als aktueller Thread bezeichnet, und Thread.currentThread() kann den aktuellen Thread zurückgeben. Da derselbe Code von verschiedenen Threads ausgeführt werden kann, ist der aktuelle Thread relativ, dh der Rückgabewert von Thread.currentThread () kann verschiedenen Threads (Objekten) entsprechen, wenn der Code tatsächlich ausgeführt wird.
Die Join-Methode entspricht dem Thread, der die Methode ausführt, und der Thread-Scheduler sagt: „Ich muss zuerst anhalten und warten, bis der andere Thread die Ausführung beendet hat, bevor ich fortfahren kann.“
Die yield static-Methode entspricht Ausführen Der Thread dieser Methode sagt zum Thread-Scheduler: „Ich habe es jetzt nicht eilig. Wenn jemand anderes die Prozessorressourcen benötigt, lassen Sie ihn sie zuerst verwenden. Wenn niemand anderes sie verwenden möchte, tue ich es natürlich nicht.“ Es macht mir nichts aus, es weiterhin zu belegen.“
 sleep Die Rolle einer statischen Methode entspricht der Aussage des Threads, der die Methode ausführt, zum Thread-Scheduler: „Ich möchte ein Nickerchen machen, mich nach einer Weile wecken und weiterarbeiten.“ ."

4、Thread类中的废弃方法

Detaillierte Analyse von Multithread-Programmiermethoden in JAVA (mit Beispielen)

虽然这些方法并没有相应的替代品,但是可以使用其他办法来实现,我们会在后续文章中学习这部分内容。

四、无处不在的线程

Java平台本身就是一个多线程的平台。除了Java开发人员自己创建和使用的线程,Java平台中其他由Java虚拟机创建、使用的线程也随处可见。当然,这些线程也是各自有其处理任务。
  Java虚拟机启动的时候会创建一个主线程(main线程),该线程负责执行Java程序的入口方法(main方法)。下面的程序打印出主线程的名称:

public class MainThreadDemo {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

  该程序会输出“main”,这说明main方法是由一个名为“main”的线程调用的,这个线程就是主线程,它是由JVM创建并启动的。
  在多线程编程中,弄清楚一段代码具体是由哪个(或者哪种)线程去负责执行的这点很重要,这关系到性能、线程安全等问题。本系列的后续文章会体现这点。
  Java 虚拟机垃圾回收器(Garbage Collector)负责对Java程序中不再使用的内存空间进行回收,而这个回收的动作实际上也是通过专门的线程(垃圾回收线程)实现的,这些线程由Java虚拟机自行创建。
  为了提高Java代码的执行效率,Java虚拟机中的JIT(Just In Time)编译器会动态地将Java字节码编译为Java虚拟机宿主机处理器可直接执行的机器码。这个动态编译的过程实际上是由Java虚拟机创建的专门的线程负责执行的。
  Java平台中的线程随处可见,这些线程各自都有其处理任务。

五、线程的层次关系

Java平台中的线程不是孤立的,线程与线程之间总是存在一些联系。假设线程A所执行的代码创建了线程B, 那么,习惯上我们称线程B为线程A的子线程,相应地线程A就被称为线程B的父线程。例如, Code 1-2中的线程thread1和thread2是main线程的子线程,main线程是它们的父线程。子线程所执行的代码还可以创建其他线程,因此一个子线程也可以是其他线程的父线程。所以,父线程、子线程是一个相对的称呼。理解线程的层次关系有助于我们理解Java应用程序的结构,也有助于我们后续阐述其他概念。
  在Java平台中,一个线程是否是一个守护线程默认取决于其父线程:默认情况下父线程是守护线程,则子线程也是守护线程;父线程是用户线程,则子线程也是用户线程。另外,父线程在创建子线程后启动子线程之前可以调用该线程的setDaemon方法,将相应的线程设置为守护线程(或者用户线程)。
  一个线程的优先级默认值为该线程的父线程的优先级,即如果我们没有设置或者更改一个线程的优先级,那么这个线程的优先级的值与父线程的优先级的值相等。
  不过,Java平台中并没有API用于获取一个线程的父线程,或者获取一个线程的所有子线程。并且,父线程和子线程之间的生命周期也没有必然的联系。比如父线程运行结束后,子线程可以继续运行,子线程运行结束也不妨碍其父线程继续运行。

六、线程的生命周期状态

 在Java平台中,一个线程从其创建、启动到其运行结束的整个生命周期可能经历若干状态。如下图所示:

Detaillierte Analyse von Multithread-Programmiermethoden in JAVA (mit Beispielen)

Der Status des Threads kann durch Aufruf von Thread.getState() abgerufen werden. Der Rückgabewerttyp von Thread.getState() ist Thread.State, ein Aufzählungstyp innerhalb der Thread-Klasse. Die durch Thread.State definierten Thread-Status umfassen Folgendes:
bis NEW: Ein Thread, der erstellt, aber nicht gestartet wurde, befindet sich in diesem Status. Da eine Thread-Instanz nur einmal gestartet werden kann, darf sich ein Thread nur einmal in diesem Zustand befinden.
RUNNABLE: Dieser Zustand kann als zusammengesetzter Zustand betrachtet werden, der zwei Unterzustände umfasst: READY und RUNNING, aber tatsächlich sind diese beiden Zustände nicht in Thread.State definiert. Ersteres bedeutet, dass der Thread in diesem Status vom Thread-Scheduler so geplant werden kann, dass er in den RUNNING-Status versetzt wird. Letzteres zeigt an, dass der Thread in diesem Zustand ausgeführt wird, dh die Anweisungen, die der Ausführungsmethode des entsprechenden Thread-Objekts entsprechen, werden vom Prozessor ausgeführt. Der Status des Threads, der Thread.yield() ausführt, kann von RUNNING in READY umgewandelt werden. Threads im Unterzustand READY werden auch als aktive Threads bezeichnet.
BLOCKED: Nachdem ein Thread einen blockierenden E/A-Vorgang initiiert oder eine exklusive Ressource (z. B. eine Sperre) beantragt, die von einem anderen Thread gehalten wird, befindet sich der entsprechende Thread in diesem Zustand. Threads im Status BLOCKED belegen keine Prozessorressourcen. Wenn der blockierende 1/0-Vorgang abgeschlossen ist oder der Thread die angeforderten Ressourcen erhält, kann der Status des Threads in RUNNABLE konvertiert werden.
WAITING: Nachdem ein Thread bestimmte Methoden ausgeführt hat, befindet er sich in diesem Zustand und wartet darauf, dass andere Threads andere spezifische Vorgänge ausführen. Zu den Methoden, die seinen Ausführungsthread in den Status WAITING ändern können, gehören: Object.wait(), Thread.join() und LockSupport.park(Object). Zu den entsprechenden Methoden, die den entsprechenden Thread von WAITING in RUNNABLE ändern können, gehören: Object.notify()/notifyAll() und LockSupport.unpark(Object)).
TIMED_WAITING: Dieser Zustand ähnelt WAITING. Der Unterschied besteht darin, dass der Thread in diesem Zustand nicht unbegrenzt darauf wartet, dass andere Threads bestimmte Vorgänge ausführen, sondern sich in einem zeitlich begrenzten Wartezustand befindet. Wenn andere Threads die vom Thread erwarteten spezifischen Vorgänge nicht innerhalb der angegebenen Zeit ausführen, wird der Status des Threads automatisch in RUNNABLE konvertiert.
TERMINATED: Der Thread, dessen Ausführung abgeschlossen ist, befindet sich in diesem Status. Da eine Thread-Instanz nur einmal gestartet werden kann, kann sich ein Thread auch nur einmal in diesem Zustand befinden. Die Ausführungsmethode kehrt normal zurück oder wird aufgrund einer Ausnahme vorzeitig beendet, wodurch sich der entsprechende Thread in diesem Zustand befindet.
Ein Thread kann sich in seinem gesamten Lebenszyklus nur einmal im Zustand NEW und TERMINATED befinden.

7. Vorteile der Multithread-Programmierung

Multithread-Programmierung hat die folgenden Vorteile:

Verbessern Sie die Effizienz des Systems. Durchsatzrate: Multithread-Programmierung ermöglicht mehrere gleichzeitige (d. h. gleichzeitige) Vorgänge in einem Prozess. Während beispielsweise ein Thread auf eine E/A-Operation wartet, können andere Threads ihre Operationen weiterhin ausführen.

Verbessern Sie die Reaktionsfähigkeit: Bei Verwendung von Multithread-Programmierung für GUI-Software (z. B. Desktop-Anwendungen) führt ein langsamer Vorgang (z. B. das Herunterladen einer großen Datei vom Server) nicht dazu, dass die Schnittstelle der Software angezeigt wird „eingefroren“ sein und nicht auf andere Benutzervorgänge reagieren können; bei Webanwendungen hat die langsame Verarbeitung einer Anfrage keinen Einfluss auf die Verarbeitung anderer Anfragen.

Nutzen Sie die Multi-Core-Prozessorressourcen voll aus: Heutzutage werden Multi-Core-Prozessorgeräte immer beliebter, und selbst Verbrauchergeräte wie Mobiltelefone verwenden häufig Multi-Core-Prozessoren. Die Implementierung der richtigen Multithread-Programmierung hilft uns, die Multi-Core-Prozessorressourcen des Geräts voll auszunutzen und Ressourcenverschwendung zu vermeiden.

Multithread-Programmierung hat auch ihre eigenen Probleme und Risiken, einschließlich der folgenden Aspekte:

Thread-Sicherheitsprobleme. Wenn mehrere Threads Daten gemeinsam nutzen und keine entsprechenden gleichzeitigen Zugriffskontrollmaßnahmen ergriffen werden, können Datenkonsistenzprobleme auftreten, z. B. das Lesen schmutziger Daten (abgelaufene Daten) und verlorene Aktualisierungen (von einigen Threads vorgenommene Aktualisierungen werden von anderen Threads gelöscht). Threads überschreiben) usw.

Problem mit der Thread-Aktivität. Der gesamte Lebenszyklus eines Threads von seiner Erstellung bis zum Ende seiner Ausführung durchläuft verschiedene Zustände. Aus der Sicht eines einzelnen Threads ist der RUNNABLE-Status der gewünschte Status. Tatsächlich kann das fehlerhafte Schreiben von Code jedoch dazu führen, dass einige Threads darauf warten, dass andere Threads die Sperre aufheben (BLOCKIERTER Zustand). Diese Situation wird als Deadlock (Deadlock) bezeichnet. Natürlich kann es auch zu Problemen kommen, wenn der Thread ständig ausgelastet ist. Es kann zu einem Livelock-Problem kommen, das heißt, ein Thread hat versucht, einen Vorgang auszuführen, kommt aber nicht voran. Darüber hinaus sind Threads eine knappe Rechenressource, und die Anzahl der Prozessoren, über die ein System verfügt, ist im Vergleich zur Anzahl der im System vorhandenen Threads immer sehr gering. In einigen Fällen kann das Problem des Thread-Aushungerns (Starvation) auftreten, das heißt, einige Threads können nie vom Prozessor ausgeführt werden und befinden sich immer im READY-Unterzustand des RUNNABLE-Zustands.

Kontextwechsel. Wenn der Prozessor von der Ausführung eines Threads zur Ausführung eines anderen Threads wechselt, wird eine vom Betriebssystem erforderliche Aktion als Kontextwechsel bezeichnet. Aufgrund der Knappheit der Prozessorressourcen kann der Kontextwechsel als unvermeidliches Nebenprodukt der Multithread-Programmierung angesehen werden. Er erhöht den Systemverbrauch und ist nicht förderlich für den Systemdurchsatz.

Weitere verwandte Fragen finden Sie auf der chinesischen PHP-Website: JAVA-Video-Tutorial

Das obige ist der detaillierte Inhalt vonDetaillierte Analyse von Multithread-Programmiermethoden in JAVA (mit Beispielen). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen