Heim >Java >javaLernprogramm >Fragen und Antworten zu den Grundlagen von Java-Multithreading und Parallelität im Vorstellungsgespräch
Fragen zu Multithreading und Parallelität gehören zu den Fragen, die Interviewer in Vorstellungsgesprächen zur Java-Technologie gerne stellen. Hier werden die meisten wichtigen Fragen aus der Perspektive eines Vorstellungsgesprächs aufgeführt. Sie sollten jedoch über ein solides Verständnis der Grundkenntnisse des Java-Multithreadings verfügen, um zukünftige Probleme bewältigen zu können.
Java-Multithreading-Interviewfragen
1. Was ist der Unterschied zwischen Prozessen und Threads?
Ein Prozess ist eine unabhängige (in sich geschlossene) laufende Umgebung, die als Programm oder Anwendung betrachtet werden kann. Ein Thread ist eine Aufgabe, die in einem Prozess ausgeführt wird. Die Java-Laufzeitumgebung ist ein einzelner Prozess, der verschiedene Klassen und Programme enthält. Threads können als leichte Prozesse bezeichnet werden. Threads erfordern weniger Ressourcen zum Erstellen und Verweilen in einem Prozess und können Ressourcen innerhalb eines Prozesses gemeinsam nutzen.
2. Was sind die Vorteile der Multithread-Programmierung?
In einem Multithread-Programm werden mehrere Threads gleichzeitig ausgeführt, um die Effizienz des Programms zu verbessern. Die CPU wechselt nicht in den Ruhezustand, da ein Thread auf Ressourcen warten muss. Mehrere Threads teilen sich den Heap-Speicher, daher ist es besser, mehrere Threads zu erstellen, um einige Aufgaben auszuführen, als mehrere Prozesse zu erstellen. Beispielsweise sind Servlets besser als CGI, da Servlets Multithreading unterstützen, CGI jedoch nicht.
3. Was ist der Unterschied zwischen Benutzer-Thread und Daemon-Thread?
Wenn wir einen Thread in einem Java-Programm erstellen, wird er als Benutzer-Thread bezeichnet. Ein Daemon-Thread ist ein Thread, der im Hintergrund ausgeführt wird und die Beendigung der JVM nicht verhindert. Wenn keine Benutzerthreads ausgeführt werden, schließt die JVM das Programm und beendet es. Von einem Daemon-Thread erstellte untergeordnete Threads sind immer noch Daemon-Threads.
4. Wie erstellen wir einen Thread?
Es gibt zwei Möglichkeiten, einen Thread zu erstellen: Eine besteht darin, die Runnable-Schnittstelle zu implementieren und sie dann an den Thread-Konstruktor zu übergeben, um ein Thread-Objekt zu erstellen. Die andere besteht darin, die Thread-Klasse direkt zu erben. Wenn Sie mehr wissen möchten, können Sie diesen Artikel zum Erstellen von Threads in Java lesen.
5. Was sind die verschiedenen Thread-Lebenszyklen?
Wenn wir einen neuen Thread in einem Java-Programm erstellen, ist sein Status „Neu“. Wenn wir die start()-Methode des Threads aufrufen, wird der Status in Runnable geändert. Der Thread-Scheduler weist Threads im ausführbaren Thread-Pool CPU-Zeit zu und ändert ihren Status in „Wird ausgeführt“. Andere Thread-Status sind „Wartend“, „Blockiert“ und „Tot“. Lesen Sie diesen Artikel, um mehr über den Thread-Lebenszyklus zu erfahren.
6. Kann ich die run()-Methode der Thread-Klasse direkt aufrufen?
Natürlich, aber wenn wir die run()-Methode von Thread aufrufen, verhält sie sich wie eine normale Methode. Um unseren Code in einem neuen Thread auszuführen, müssen wir die Thread.start()-Methode verwenden.
7. Wie kann ich einen laufenden Thread für einen bestimmten Zeitraum anhalten?
Wir können die Sleep()-Methode der Thread-Klasse verwenden, um den Thread für einen bestimmten Zeitraum anzuhalten. Es ist zu beachten, dass der Thread dadurch nicht beendet wird, sobald der Thread aus dem Ruhezustand aufgeweckt wird. Der Status des Threads wird in „Ausführbar“ geändert und er wird gemäß dem Thread-Zeitplan ausgeführt.
8. Was verstehen Sie unter Thread-Priorität?
Jeder Thread hat eine Priorität, wenn er ausgeführt wird. Dies hängt jedoch von der Implementierung der Thread-Planung ab, die mit dem Betriebssystem zusammenhängt (abhängig vom Betriebssystem). Wir können die Priorität von Threads definieren, aber das garantiert nicht, dass Threads mit hoher Priorität vor Threads mit niedriger Priorität ausgeführt werden. Die Thread-Priorität ist eine int-Variable (von 1 bis 10), 1 steht für die niedrigste Priorität, 10 für die höchste Priorität.
9. Was sind Thread Scheduler und Time Slicing?
Der Thread-Scheduler ist ein Betriebssystemdienst, der für die Zuweisung von CPU-Zeit an Threads im ausführbaren Zustand verantwortlich ist. Sobald wir einen Thread erstellt und gestartet haben, hängt seine Ausführung von der Implementierung des Thread-Schedulers ab. Time-Slicing bezieht sich auf den Prozess der Zuweisung verfügbarer CPU-Zeit zu verfügbaren ausführbaren Threads. Die Zuweisung von CPU-Zeit kann auf der Thread-Priorität oder der Wartezeit des Threads basieren. Die Thread-Planung wird nicht von der Java Virtual Machine gesteuert, daher ist es besser, sie von der Anwendung zu steuern (d. h. Ihr Programm nicht von der Thread-Priorität abhängig zu machen).
10. Was ist Kontextwechsel beim Multithreading?
Beim Kontextwechsel handelt es sich um den Prozess des Speicherns und Wiederherstellens des CPU-Status, der es der Thread-Ausführung ermöglicht, die Ausführung an der Stelle der Unterbrechung fortzusetzen. Kontextwechsel ist ein wesentliches Merkmal von Multitasking-Betriebssystemen und Multithread-Umgebungen.
11. Wie stellen Sie sicher, dass der Thread, in dem sich die main()-Methode befindet, der letzte Thread ist, der im Java-Programm endet?
Wir können die joint()-Methode der Thread-Klasse verwenden, um sicherzustellen, dass alle vom Programm erstellten Threads enden, bevor die main()-Methode beendet wird. Hier ist ein Artikel über die joint()-Methode der Thread-Klasse.
12. Wie kommunizieren Threads miteinander?
Wenn Threads Ressourcen gemeinsam nutzen können, ist die Kommunikation zwischen Threads ein wichtiges Mittel, um sie zu koordinieren. Die Methode wait()notify()notifyAll() in der Object-Klasse kann verwendet werden, um zwischen Threads über den Status von Ressourcensperren zu kommunizieren. Klicken Sie hier, um mehr über Thread Wait, Notify und NotifyAll zu erfahren
13. Warum sind die Thread-Kommunikationsmethoden wait(), notify() und notifyAll() in der Object-Klasse definiert?
Jedes Objekt in Java verfügt über eine Sperre (Monitor, der auch ein Monitor sein kann), und Methoden wie wait() und notify() werden verwendet, um auf die Sperre des Objekts zu warten oder andere Threads zu benachrichtigen, die der Monitor überwacht des Objekts vorhanden ist. Für kein Objekt in Java-Threads sind Sperren oder Synchronisierer verfügbar. Aus diesem Grund sind diese Methoden Teil der Object-Klasse, sodass jede Klasse in Java über grundlegende Methoden für die Kommunikation zwischen Threads verfügt
14. Warum wait(), notify() und notifyAll() in verwendet werden müssen synchronisierte Methoden Oder wird es in einem synchronisierten Block aufgerufen?
Wenn ein Thread die Methode wait() eines Objekts aufrufen muss, muss der Thread die Sperre des Objekts besitzen. Anschließend wird die Objektsperre aufgehoben und in den Wartezustand versetzt, bis andere Threads die Methode notify() aufrufen das Objekt. Wenn ein Thread die notify()-Methode des Objekts aufrufen muss, gibt er in ähnlicher Weise die Sperre des Objekts frei, sodass andere wartende Threads die Objektsperre erhalten können. Da alle diese Methoden erfordern, dass der Thread die Sperre des Objekts hält, was nur durch Synchronisierung erreicht werden kann, können sie nur in synchronisierten Methoden oder synchronisierten Blöcken aufgerufen werden.
15. Warum sind die Methoden sleep() und yield() der Thread-Klasse statisch?
Die Methoden sleep() und yield() der Thread-Klasse werden auf dem aktuell ausgeführten Thread ausgeführt. Es macht also keinen Sinn, diese Methoden in anderen wartenden Threads aufzurufen. Deshalb sind diese Methoden statisch. Sie können im aktuell ausgeführten Thread arbeiten und verhindern, dass Programmierer fälschlicherweise denken, dass diese Methoden in anderen nicht laufenden Threads aufgerufen werden können.
16. Wie gewährleistet man die Thread-Sicherheit?
Es gibt viele Möglichkeiten, die Thread-Sicherheit in Java sicherzustellen – Synchronisierung, Verwendung atomarer gleichzeitiger Klassen, Implementierung gleichzeitiger Sperren, Verwendung des Schlüsselworts volatile, Verwendung unveränderlicher Klassen und threadsicherer Klassen. Weitere Informationen finden Sie im Thread-Sicherheits-Tutorial.
17. Welche Rolle spielt das Schlüsselwort volatile in Java?
Wenn wir das Schlüsselwort volatile verwenden, um eine Variable zu ändern, liest der Thread die Variable direkt und speichert sie nicht zwischen. Dadurch wird sichergestellt, dass die vom Thread gelesenen Variablen mit denen im Speicher übereinstimmen.
18. Was ist die bessere Wahl: synchronisierte Methoden oder synchronisierte Blöcke?
Ein synchronisierter Block ist eine bessere Wahl, da er nicht das gesamte Objekt sperrt (natürlich können Sie auch festlegen, dass er das gesamte Objekt sperrt). Synchronisierte Methoden sperren das gesamte Objekt, auch wenn die Klasse mehrere unabhängige synchronisierte Blöcke enthält, was normalerweise dazu führt, dass sie nicht mehr ausgeführt werden und warten müssen, bis die Sperre für das Objekt erhalten wird.
19. Wie erstelle ich einen Daemon-Thread?
Verwenden Sie die setDaemon(true)-Methode der Thread-Klasse, um den Thread als Daemon-Thread festzulegen. Beachten Sie, dass diese Methode vor dem Aufruf der start()-Methode aufgerufen werden muss, da sonst eine IllegalThreadStateException auftritt geworfen.
20. Was ist ThreadLocal?
ThreadLocal wird zum Erstellen lokaler Variablen von Threads verwendet. Wir wissen, dass alle Threads eines Objekts seine globalen Variablen gemeinsam nutzen, daher sind diese Variablen nicht threadsicher . Wir können Synchrontechnik nutzen. Wenn wir jedoch keine Synchronisierung verwenden möchten, können wir ThreadLocal-Variablen auswählen.
Jeder Thread hat seine eigenen Thread-Variablen und kann die Methode get()set() verwenden, um seine Standardwerte abzurufen oder seine Werte innerhalb des Threads zu ändern. ThreadLocal-Instanzen möchten normalerweise, dass der zugehörige Thread-Status private statische Eigenschaften ist. Im ThreadLocal-Beispielartikel sehen Sie ein kleines Programm zu ThreadLocal.
21. Was ist eine Thread-Gruppe? Warum wird die Verwendung empfohlen?
ThreadGroup ist eine Klasse, deren Zweck darin besteht, Informationen über Thread-Gruppen bereitzustellen.
Die ThreadGroup-API ist relativ schwach und bietet nicht mehr Funktionen als Thread. Es hat zwei Hauptfunktionen: Die eine besteht darin, die Liste der aktiven Threads in der Thread-Gruppe abzurufen, die andere darin, den nicht abgefangenen Ausnahmehandler (ncaught Exception handler) für den Thread festzulegen. In Java 1.5 hat die Thread-Klasse jedoch auch die Methode setUncaughtExceptionHandler(UncaughtExceptionHandler eh) hinzugefügt, sodass ThreadGroup veraltet ist und die weitere Verwendung nicht empfohlen wird.
t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){ @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("exception occured:"+e.getMessage()); } });
22. Was ist ein Java-Thread-Dump (Thread Dump) und wie bekomme ich ihn?
Ein Thread-Dump ist eine Liste aktiver JVM-Threads, die für die Analyse von Systemengpässen und Deadlocks sehr nützlich ist. Es gibt viele Möglichkeiten, einen Thread-Dump zu erhalten – mit Profiler, dem Befehl Kill -3, dem Jstack-Tool usw. Ich bevorzuge das Jstack-Tool, weil es einfach zu bedienen ist und mit JDK geliefert wird. Da es sich um ein Terminal-basiertes Tool handelt, können wir einige Skripte schreiben, um regelmäßig Thread-Dumps zur Analyse zu generieren. Lesen Sie dieses Dokument, um mehr über das Generieren von Thread-Dumps zu erfahren.
23. Was ist Deadlock? Wie kann man Deadlocks analysieren und vermeiden?
Deadlock bezieht sich auf eine Situation, in der mehr als zwei Threads dauerhaft blockiert sind. Diese Situation erfordert mindestens zwei weitere Threads und mehr als zwei Ressourcen.
Um den Deadlock zu analysieren, müssen wir uns den Thread-Dump der Java-Anwendung ansehen. Wir müssen herausfinden, welche Threads sich im Status BLOCKED befinden und auf welche Ressourcen sie warten. Jede Ressource hat eine eindeutige ID. Mithilfe dieser ID können wir herausfinden, welche Threads ihre Objektsperre bereits besitzen.
Das Vermeiden verschachtelter Sperren, die Verwendung von Sperren nur dort, wo sie benötigt werden, und das Vermeiden unbegrenzter Wartezeiten sind gängige Methoden zur Vermeidung von Deadlocks. Lesen Sie diesen Artikel, um zu erfahren, wie Sie Deadlocks analysieren.
24. Was ist die Java-Timer-Klasse? Wie erstelle ich eine Aufgabe mit einem bestimmten Zeitintervall?
java.util.Timer ist eine Toolklasse, mit der ein Thread so geplant werden kann, dass er zu einem bestimmten Zeitpunkt in der Zukunft ausgeführt wird. Mit der Timer-Klasse können einmalige oder periodische Aufgaben geplant werden.
java.util.TimerTask ist eine abstrakte Klasse, die die Runnable-Schnittstelle implementiert. Wir müssen diese Klasse erben, um unsere eigenen geplanten Aufgaben zu erstellen und Timer zum Planen ihrer Ausführung zu verwenden.
Hier sind Beispiele für Java-Timer.
25. Was ist ein Thread-Pool? Wie erstelle ich einen Java-Thread-Pool?
Ein Thread-Pool verwaltet eine Gruppe von Arbeitsthreads und umfasst auch eine Warteschlange zum Platzieren von Aufgaben, die auf ihre Ausführung warten.
java.util.concurrent.Executors bietet eine Implementierung der java.util.concurrent.Executor-Schnittstelle zum Erstellen von Thread-Pools. Das Thread-Pool-Beispiel zeigt, wie Sie einen Thread-Pool erstellen und verwenden, oder lesen Sie das ScheduledThreadPoolExecutor-Beispiel, um zu erfahren, wie Sie eine periodische Aufgabe erstellen.
Fragen im Vorstellungsgespräch zu Java Concurrency
1. Was ist eine atomare Operation? Was sind die atomaren Klassen in der Java Concurrency API?
Eine atomare Operation bezieht sich auf eine Operationsaufgabeneinheit, die nicht von anderen Operationen betroffen ist. Atomare Operationen sind ein notwendiges Mittel, um Dateninkonsistenzen in einer Multithread-Umgebung zu vermeiden.
int++ ist keine atomare Operation. Wenn also ein Thread seinen Wert liest und 1 hinzufügt, liest ein anderer Thread möglicherweise den vorherigen Wert, was zu einem Fehler führt.
Um dieses Problem zu lösen, müssen wir sicherstellen, dass die Additionsoperation atomar ist. Vor JDK1.5 konnten wir hierfür die Synchronisationstechnologie verwenden. Ab JDK 1.5 stellt das Paket java.util.concurrent.atomic Ladeklassen vom Typ int und long bereit, die automatisch garantieren, dass ihre Vorgänge atomar sind und keine Synchronisierung erfordern. Sie können diesen Artikel lesen, um mehr über die Atomklassen von Java zu erfahren.
2. Was ist die Lock-Schnittstelle in der Java Concurrency API? Was sind die Vorteile gegenüber der Synchronisierung?
Die Lock-Schnittstelle bietet skalierbarere Lock-Operationen als synchronisierte Methoden und synchronisierte Blöcke. Sie ermöglichen flexiblere Strukturen, die völlig unterschiedliche Eigenschaften haben können und mehrere verwandte Klassen bedingter Objekte unterstützen können.
Seine Vorteile sind:
kann Sperren gerechter machen
kann dafür sorgen, dass Threads auf Interrupts reagieren, während sie auf Sperren warten
kann dazu führen, dass Threads versuchen, Sperren zu erhalten, und sofort zurückkehren oder eine Zeit lang warten, bis die Sperre nicht erworben werden kann
Sie können Sperren in unterschiedlichem Umfang und in unterschiedlicher Reihenfolge erwerben und freigeben
Weitere Beispiele für Sperren lesen
3. Was ist das Executors-Framework?
Das Executor-Framework und die java.util.concurrent.Executor-Schnittstelle wurden in Java 5 eingeführt. Das Executor-Framework ist ein Framework für asynchrone Aufgaben, die gemäß einer Reihe von Ausführungsstrategien aufgerufen, geplant, ausgeführt und gesteuert werden.
Unbegrenzte Thread-Erstellung kann zu einem Überlauf des Anwendungsspeichers führen. Daher ist die Erstellung eines Thread-Pools eine bessere Lösung, da die Anzahl der Threads begrenzt werden kann und diese Threads recycelt und wiederverwendet werden können. Es ist sehr praktisch, einen Thread-Pool mit dem Executors-Framework zu erstellen. Lesen Sie diesen Artikel, um zu erfahren, wie Sie einen Thread-Pool mit dem Executors-Framework erstellen.
4. Was ist eine Blockierungswarteschlange? Wie verwende ich die Blockierungswarteschlange, um das Producer-Consumer-Modell zu implementieren?
Die Eigenschaften von java.util.concurrent.BlockingQueue sind: Wenn die Warteschlange leer ist, wird der Vorgang zum Abrufen oder Löschen von Elementen aus der Warteschlange blockiert, oder wenn die Warteschlange voll ist, werden Elemente zur Warteschlange hinzugefügt Der Vorgang wird blockiert.
Die blockierende Warteschlange akzeptiert keine Nullwerte. Wenn Sie versuchen, der Warteschlange einen Nullwert hinzuzufügen, wird eine NullPointerException ausgelöst.
Die Implementierung blockierender Warteschlangen ist threadsicher, alle Abfragemethoden sind atomar und verwenden interne Sperren oder andere Formen der Parallelitätskontrolle.
Die BlockingQueue-Schnittstelle ist Teil des Java-Collections-Frameworks. Sie wird hauptsächlich zur Implementierung des Producer-Consumer-Problems verwendet.
Lesen Sie diesen Artikel, um zu erfahren, wie Sie Blockierungswarteschlangen verwenden, um das Producer-Consumer-Problem zu implementieren.
5. Was sind Callable und Future?
Java 5 hat die java.util.concurrent.Callable-Schnittstelle im Parallelitätspaket eingeführt, sie ist der Runnable-Schnittstelle sehr ähnlich, kann aber zurückkehren ein Objekt. Oder eine Ausnahme auslösen.
Die Callable-Schnittstelle verwendet Generika, um ihren Rückgabetyp zu definieren. Die Executors-Klasse bietet einige nützliche Methoden zum Ausführen von Aufgaben innerhalb von Callables im Thread-Pool. Da die Callable-Aufgabe parallel ist, müssen wir auf das zurückgegebene Ergebnis warten. Das java.util.concurrent.Future-Objekt löst dieses Problem für uns. Nachdem der Thread-Pool die Callable-Aufgabe übermittelt hat, wird ein Future-Objekt zurückgegeben. Mithilfe dieses Objekts können wir den Status der Callable-Aufgabe ermitteln und das vom Callable zurückgegebene Ausführungsergebnis abrufen. Future stellt die Methode get() bereit, damit wir auf das Ende des Callable warten und dessen Ausführungsergebnisse erhalten können.
Lesen Sie diesen Artikel, um mehr über Callable- und Future-Beispiele zu erfahren.
6. Was ist FutureTask?
FutureTask ist eine grundlegende Implementierung von Future. Wir können es mit Executors verwenden, um asynchrone Aufgaben zu verarbeiten. Normalerweise müssen wir die FutureTask-Klasse nicht verwenden, aber sie ist sehr nützlich, wenn wir planen, einige Methoden der Future-Schnittstelle zu überschreiben und die ursprüngliche Basisimplementierung beizubehalten. Wir können einfach davon erben und die Methoden, die wir benötigen, überschreiben. Lesen Sie das Java FutureTask-Beispiel, um zu erfahren, wie Sie es verwenden.
7. Was ist die Implementierung gleichzeitiger Container?
Java-Sammlungsklassen sind ausfallsicher. Das heißt, wenn die Sammlung geändert wird und ein Thread einen Iterator zum Durchlaufen der Sammlung verwendet, löst die next()-Methode des Iterators eine ConcurrentModificationException-Ausnahme aus.
Gleichzeitige Container unterstützen gleichzeitiges Durchlaufen und gleichzeitige Aktualisierungen.
Die Hauptklassen sind ConcurrentHashMap, CopyOnWriteArrayList und CopyOnWriteArraySet. Lesen Sie diesen Artikel, um zu erfahren, wie Sie ConcurrentModificationException vermeiden.
8. Was ist die Executors-Klasse?
Executoren stellen einige Toolmethoden für die Klassen Executor, ExecutorService, ScheduledExecutorService, ThreadFactory und Callable bereit.
Executors können zum einfachen Erstellen von Thread-Pools verwendet werden.