Es gibt 21 Multithreading-Artikel, die in der Kategorie „Java-Multithreading“ geschrieben wurden. Ich persönlich denke, je mehr Inhalt und komplexeres Wissen Sie lernen, desto mehr müssen Sie eine fundierte Zusammenfassung erstellen. damit Sie sich tief daran erinnern und das Wissen in etwas Eigenes verwandeln können. In diesem Artikel werden hauptsächlich Multithreading-Probleme zusammengefasst, sodass 40 Multithreading-Probleme aufgeführt sind.
Einige dieser Multithreading-Probleme stammen von großen Websites, andere sind auf meine eigenen Gedanken zurückzuführen. Möglicherweise gibt es einige Fragen im Internet, möglicherweise gibt es Antworten auf einige Fragen und möglicherweise einige, die alle Internetnutzer gelesen haben. Beim Schreiben dieses Artikels liegt der Schwerpunkt jedoch darauf, alle Fragen nach Ihrem eigenen Verständnis zu beantworten, und das werden Sie auch tun Schauen Sie sich die Antworten nicht online an. Vielleicht sind einige der Fragen falsch, und ich hoffe, Sie können mich korrigieren.
1. Wozu dient Multithreading?
Eine Frage, die für viele vielleicht unsinnig erscheint: Was nützt es, solange ich Multithreading nutzen kann? Meiner Meinung nach ist diese Antwort noch mehr Unsinn. Das sogenannte „Wissen, wie es ist, wissen, warum es ist“, „Wissen, wie man es benutzt“ ist nur „Wissen, wie es ist“, „warum man es benutzt“ ist „Wissen, wie es ist, nur wenn man es erreicht“. Die Ebene „Wissen, wie es ist, wissen, warum es so ist“ kann als „Wissen, wie es ist“ bezeichnet werden. Ein Wissenspunkt kann frei genutzt werden. OK, lass uns über meine Ansichten zu diesem Thema sprechen:
(1) Nutzen Sie die Multi-Core-CPU
Mit der Weiterentwicklung der Industrie sind heutige Notebooks, Desktops und sogar kommerzielle Anwendungsserver keine Seltenheit, wenn es sich um ein Single-Thread-Programm handelt Bei einer Dual-Core-CPU werden 50 % und bei einer 4-Core-CPU 75 % verschwendet. Das sogenannte „Multi-Threading“ auf einer Single-Core-CPU ist gefälschtes Multi-Threading. Der Prozessor verarbeitet nur einen Teil der Logik gleichzeitig, aber der Wechsel zwischen Threads ist schneller und es sieht so aus, als wären es mehrere Threads laufen „gleichzeitig“. Multi-Threading auf einer Multi-Core-CPU ist echtes Multi-Threading. Es ermöglicht die gleichzeitige Arbeit mehrerer Logikelemente, sodass die Vorteile der Multi-Core-CPU wirklich genutzt werden können der CPU.
(2) Blockierung verhindern
Aus Sicht der Effizienz der Programmausführung nutzt eine Single-Core-CPU nicht nur die Vorteile von Multi-Threading nicht aus, sondern verringert auch die Gesamteffizienz des Programms, da die Ausführung von Multi-Threads auf einer Single-Core-CPU zu Thread-Kontext führt schalten. Bei Single-Core-CPUs müssen wir jedoch weiterhin Multithreading verwenden, um Blockierungen zu verhindern. Stellen Sie sich vor, wenn eine Single-Core-CPU einen einzelnen Thread verwendet, dann wird Ihr gesamtes Programm dies tun, solange dieser Thread beispielsweise beim Lesen bestimmter Daten aus der Ferne blockiert ist und der Peer noch nicht zurückgekehrt ist und kein Timeout festgelegt hat blockiert werden, bevor die Daten zurückgegeben werden. Multithreading kann dieses Problem verhindern. Auch wenn die Codeausführung eines Threads beim Lesen von Daten blockiert wird, hat dies keinen Einfluss auf die Ausführung anderer Aufgaben.
(3) Einfach zu modellieren
Dies ist ein weiterer, weniger offensichtlicher Vorteil. Angenommen, es gibt eine große Aufgabe A, Single-Thread-Programmierung, dann gibt es viel zu beachten und es ist mühsam, das gesamte Programmmodell zu erstellen. Wenn Sie diese große Aufgabe A jedoch in mehrere kleine Aufgaben, Aufgabe B, Aufgabe C und Aufgabe D, aufteilen, jeweils Programmmodelle erstellen und diese Aufgaben separat über Multithreads ausführen, wird es viel einfacher.
2. So erstellen Sie einen Thread
Eine relativ häufige Frage, im Allgemeinen gibt es zwei Arten:
(1) Thread-Klasse erben
(2) Implementieren Sie die Runnable-Schnittstelle
Es versteht sich von selbst, dass letzteres besser ist, da die Art der Schnittstellenimplementierung flexibler ist als die Art der Vererbung von Klassen und auch die Kopplung zwischen Programmen verringern kann. Schnittstellenorientierte Programmierung ist ebenfalls der Kern der sechs Prinzipien von Designmustern.
3. Der Unterschied zwischen der start()-Methode und der run()-Methode
Erst wenn die start()-Methode aufgerufen wird, wird die Multithreading-Funktion angezeigt und der Code in den run()-Methoden verschiedener Threads wird abwechselnd ausgeführt. Wenn Sie nur die run()-Methode aufrufen, wird der Code weiterhin synchron ausgeführt. Sie müssen warten, bis der gesamte Code in der run()-Methode eines Threads ausgeführt wird, bevor ein anderer Thread den Code in seiner run()-Methode ausführen kann.
4. Der Unterschied zwischen ausführbarer Schnittstelle und aufrufbarer Schnittstelle
Dies ist eine etwas tiefgründige Frage, und sie zeigt auch, wie breit gefächert das Wissen ist, das ein Java-Programmierer erwerben kann.
Der Rückgabewert der run()-Methode in der Runnable-Schnittstelle ist ungültig, und sie führt lediglich den Code in der run()-Methode aus. Die call()-Methode in der Callable-Schnittstelle hat einen Rückgabewert und ist ein generischer Wert. kann in Verbindung mit Future und FutureTask verwendet werden, um die Ergebnisse der asynchronen Ausführung zu erhalten.
Dies ist tatsächlich eine sehr nützliche Funktion, da ein wichtiger Grund, warum Multithreading schwieriger und komplexer ist als Singlethreading, darin besteht, dass Multithreading voller Unbekannter ist. Wurde ein bestimmter Thread ausgeführt? Wie lange wurde ein Thread ausgeführt? Wurden bei der Ausführung eines Threads die erwarteten Daten zugewiesen? Wir können es nicht wissen. Wir können nur warten, bis diese Multithread-Aufgabe abgeschlossen ist. Callable+Future/FutureTask kann die Ergebnisse von Multithread-Operationen abrufen und die Aufgabe des Threads abbrechen, wenn die Wartezeit zu lang ist und die erforderlichen Daten nicht abgerufen werden, was wirklich sehr nützlich ist.
5. Der Unterschied zwischen CyclicBarrier und CountDownLatch
Zwei Klassen, die etwas ähnlich aussehen, beide unter java.util.concurrent, können verwendet werden, um anzuzeigen, dass der Code bis zu einem bestimmten Punkt ausgeführt wird. Der Unterschied zwischen den beiden ist:
(1) Nachdem ein Thread von CyclicBarrier bis zu einem bestimmten Punkt ausgeführt wurde, wird der Thread nicht mehr ausgeführt, bis alle Threads diesen Punkt erreichen bestimmter Wert -1, und der Thread läuft weiter
(2) CyclicBarrier kann nur eine Aufgabe hervorrufen, und CountDownLatch kann mehrere Aufgaben hervorrufen
(3) CyclicBarrier kann wiederverwendet werden, CountDownLatch kann jedoch nicht wiederverwendet werden. Wenn der Zählwert 0 ist, kann CountDownLatch nicht wiederverwendet werden
6. Die Rolle des volatilen Schlüsselworts
Ein sehr wichtiges Thema, das jeder Java-Programmierer beherrschen muss, der Multithreading lernt und anwendet. Die Voraussetzung für das Verständnis der Rolle des Schlüsselworts volatile ist das Verständnis des Java-Speichermodells. Ich werde hier nicht auf das Java-Speichermodell eingehen. Sie können sich auf Punkt 31 beziehen. Das Schlüsselwort volatile hat zwei Hauptfunktionen:
(1) Beim Multithreading geht es hauptsächlich um die beiden Merkmale Sichtbarkeit und Atomizität. Variablen, die mit dem Schlüsselwort volatile geändert werden, stellen ihre Sichtbarkeit zwischen Multithreads sicher, dh jedes Mal, wenn eine flüchtige Variable gelesen wird, muss es sich um die neuesten Daten handeln
(2) Die zugrunde liegende Ausführung des Codes ist nicht so einfach wie die Hochsprache, die wir sehen – Java-Programm. Seine Ausführung ist Java-Code -> Bytecode -> führt den entsprechenden C/C++-Code aus. ->C/C++-Code wird in Assemblersprache kompiliert-->interagiert mit Hardware-Schaltkreisen. Um eine bessere Leistung zu erzielen, kann die JVM Anweisungen neu anordnen und bei Multithreading können einige unerwartete Probleme auftreten. Durch die Verwendung von volatile wird die Verbotssemantik neu geordnet, was natürlich auch die Effizienz der Codeausführung bis zu einem gewissen Grad verringert
Aus praktischer Sicht besteht eine wichtige Rolle von Volatile in der Kombination mit CAS, um die Atomizität sicherzustellen. Weitere Informationen finden Sie in den Klassen unter dem Paket java.util.concurrent.atomic, z. B. AtomicInteger.
7. Was ist Thread-Sicherheit?
Dies ist eine weitere theoretische Frage, und es gibt viele verschiedene Antworten, die meiner Meinung nach am besten erklären: Wenn Ihr Code in mehreren Threads und in einem einzelnen Thread ausgeführt wird, erhalten Sie immer das gleiche Ergebnis. dann ist Ihr Code threadsicher.
Erwähnenswert bei dieser Frage ist, dass es mehrere Ebenen der Thread-Sicherheit gibt:
(1) Unveränderlich
Klassen wie String, Integer und Long sind alle endgültige Typen, es sei denn, sie erstellen einen neuen. Daher können diese unveränderlichen Objekte ohne Synchronisierungsmittel direkt verwendet werden 🎜>
(2) Absolute Thread-Sicherheit Unabhängig von der Laufzeitumgebung sind vom Aufrufer keine zusätzlichen Synchronisationsmaßnahmen erforderlich. Um dies zu erreichen, müssen Sie normalerweise viele zusätzliche Kosten bezahlen. Tatsächlich sind die meisten Klassen in Java, die als Thread-sicher gekennzeichnet sind, nicht Thread-sicher. sicher, wie CopyOnWriteArrayList und CopyOnWriteArraySet (3) Relative Thread-Sicherheit Relative Thread-Sicherheit ist das, was wir normalerweise als Thread-Sicherheit bezeichnen. Die Add- und Remove-Methoden sind atomare Operationen und werden nicht unterbrochen, sie sind jedoch darauf beschränkt, wenn ein Thread dies hinzufügt Gleichzeitig tritt in 99 % der Fälle eine ConcurrentModificationException auf, bei der es sich um einen Fail-Fast-Mechanismus handelt. (4) Thread ist nicht sicher Dazu gibt es nichts zu sagen. ArrayList, LinkedList, HashMap usw. sind allesamt Thread-nicht-sichere Klassen8. So erhalten Sie eine Thread-Dump-Datei in Java
Bei Problemen wie Endlosschleifen, Deadlocks, Blockierungen und langsamem Seitenöffnen ist Thread-Dumping der beste Weg, das Problem zu lösen. Der sogenannte Thread-Dump ist der Thread-Stack. Es gibt zwei Schritte, um den Thread-Stack zu erhalten: (1) Um die PID des Threads zu erhalten, können Sie den Befehl jps verwenden. In der Linux-Umgebung können Sie auch ps -ef grep java verwenden (2) Um den Thread-Stack zu drucken, können Sie den Befehl jstack pid verwenden. In der Linux-Umgebung können Sie auch kill -3 pid verwenden Darüber hinaus stellt die Thread-Klasse eine getStackTrace()-Methode bereit, mit der auch der Thread-Stack abgerufen werden kann. Dies ist eine Instanzmethode, daher ist diese Methode an eine bestimmte Thread-Instanz gebunden. Jedes Mal, wenn sie abgerufen wird, wird der Stapel abgerufen, der derzeit von einem bestimmten Thread ausgeführt wird,9. Was passiert, wenn ein Thread auf eine Laufzeitausnahme stößt?
Wenn diese Ausnahme nicht abgefangen wird, stoppt die Ausführung des Threads. Ein weiterer wichtiger Punkt ist: Wenn dieser Thread einen Monitor für ein bestimmtes Objekt enthält, wird der Objektmonitor sofort freigegeben
10. So teilen Sie Daten zwischen zwei Threads
Teilen Sie einfach Objekte zwischen Threads und wachen Sie dann auf und warten Sie bis Wait/Notify/NotifyAll, Wait/Signal/SignalAll. Beispielsweise ist die Blockierungswarteschlange BlockingQueue für den Datenaustausch zwischen Threads konzipiert
11. Was ist der Unterschied zwischen der Schlafmethode und der Wartemethode
? Diese Frage wird häufig gestellt, um die CPU für einen bestimmten Zeitraum aufzugeben. Der Unterschied besteht darin, dass die Schlafmethode nicht aufgibt der Monitor dieses Objekts, und die Wartemethode gibt den Monitor des Objekts auf
12. Welche Rolle spielt das Produzenten-Konsumenten-Modell?
Diese Frage ist sehr theoretisch, aber sehr wichtig:
(1) Verbesserung der Betriebseffizienz des gesamten Systems durch Ausgleich der Produktionskapazität der Produzenten und der Konsumkapazität der Verbraucher. Dies ist die wichtigste Rolle des Produzenten-Konsumenten-Modells
(2) Entkopplung, eine Nebenfunktion des Produzenten-Konsumenten-Modells. Entkopplung bedeutet, dass es weniger Verbindungen zwischen Produzenten und Verbrauchern gibt, desto mehr können sie sich unabhängig voneinander ohne gegenseitige Einschränkungen entwickeln
13. Wozu dient ThreadLocal?
Einfach ausgedrückt ist ThreadLocal eine Methode zum Austausch von Raum gegen Zeit. Jeder Thread verwaltet eine ThreadLocal.ThreadLocalMap, die durch die offene Adressmethode implementiert wird, die die Daten isoliert und nicht weitergibt. Natürlich gibt es keine Thread-Sicherheitsprobleme
14. Warum werden die Methoden wait() und notify()/notifyAll() in einem synchronisierten Block aufgerufen? Dies ist vom JDK obligatorisch. Sowohl die Methode wait() als auch die Methode notify()/notifyAll() müssen die Sperre des Objekts erhalten, bevor sie
aufrufen
15. Was ist der Unterschied zwischen der Methode wait() und der Methode notify()/notifyAll(), wenn der Objektmonitor aufgegeben wird? Der Unterschied zwischen der Methode wait() und der Methode notify()/notifyAll() beim Aufgeben des Objektmonitors besteht darin, dass die Methode wait() den Objektmonitor sofort freigibt, während die Methode notify()/notifyAll() darauf wartet Der verbleibende Code des Threads, um die Ausführung abzuschließen, wird den Objektmonitor aufgeben.
16. Warum Thread-Pool verwenden?
Vermeiden Sie das häufige Erstellen und Zerstören von Threads, um eine Wiederverwendung von Thread-Objekten zu erreichen. Darüber hinaus kann durch die Verwendung des Thread-Pools auch die Anzahl der Parallelität je nach Projekt flexibel gesteuert werden.
17. So erkennen Sie, ob ein Thread einen Objektmonitor enthält
Ich habe auch eine Multithreading-Interviewfrage im Internet gesehen und erfahren, dass es eine Möglichkeit gibt, festzustellen, ob ein Thread einen Objektmonitor enthält: Die Thread-Klasse stellt genau dann eine HoldLock(Object obj)-Methode bereit, wenn der Monitor ein Objekt obj ist wird von einem bestimmten Thread gehalten. Es wird nur dann true zurückgegeben, wenn es von einem Thread gehalten wird. Beachten Sie, dass es sich um eine statische Methode handelt, was bedeutet, dass sich „ein bestimmter Thread“ auf den aktuellen Thread bezieht.
18. Der Unterschied zwischen synchronisiertem und ReentrantLock
synchronisiert ist das gleiche Schlüsselwort wie if, else, for und while, und ReentrantLock ist eine Klasse. Dies ist der wesentliche Unterschied zwischen den beiden. Da ReentrantLock eine Klasse ist, bietet sie mehr und flexiblere Funktionen als synchronisiert. Sie kann vererbt werden, kann Methoden haben und verschiedene Klassenvariablen haben. Die Skalierbarkeit von ReentrantLock im Vergleich zu synchronisiert spiegelt sich in mehreren Punkten wider:
(1) ReentrantLock kann die Wartezeit für den Erwerb der Sperre festlegen und so Deadlocks vermeiden
(2) ReentrantLock kann Informationen zu verschiedenen Sperren erhalten (3) ReentrantLock kann flexibel mehrere Benachrichtigungen implementieren
Darüber hinaus sind die Verriegelungsmechanismen der beiden tatsächlich unterschiedlich. Die unterste Ebene von ReentrantLock ruft die Park-Methode von Unsafe auf, um zu sperren, und synchronisiert sollte das Markierungswort im Objekt-Header bearbeiten. Ich bin mir da nicht sicher.
19. Was ist die Parallelität von ConcurrentHashMap
Die Parallelität von ConcurrentHashMap ist die Größe des Segments. Der Standardwert beträgt 16, was bedeutet, dass bis zu 16 Threads gleichzeitig ausgeführt werden können. Dies ist auch der größte Vorteil von ConcurrentHashMap gegenüber Hashtable Threads gleichzeitig verwenden, um den Inhalt von Hashtable-Daten zu erhalten.
20. Was ist ReadWriteLock
Lassen Sie uns zunächst klarstellen, dass ReentrantLock nicht schlecht ist, es ist nur so, dass ReentrantLock in einigen Fällen Einschränkungen aufweist. Wenn ReentrantLock verwendet wird, kann dies dazu dienen, Dateninkonsistenzen zu verhindern, die durch das Schreiben von Daten durch Thread A und das Lesen von Daten durch Thread B verursacht werden. Wenn jedoch Thread C Daten liest und Thread D auch Daten liest, werden die Daten dort nicht geändert Eine Sperre ist nicht erforderlich, sie ist jedoch weiterhin gesperrt, was die Leistung des Programms verringert.
Aus diesem Grund wurde die Lese-/Schreibsperre ReadWriteLock geboren. ReadWriteLock ist eine Lese-/Schreibsperre-Schnittstelle, die eine gemeinsame Lese- und Schreibsperre realisiert. Es gibt keinen gegenseitigen Ausschluss zwischen Lesen und Lesen. Lesen und Schreiben, Schreiben und Lesen, Schreiben und Schreiben schließen sich gegenseitig aus, was die Leistung beim Lesen und Schreiben verbessert.
21. Was ist FutureTask
Tatsächlich stellt FutureTask, wie bereits erwähnt, eine asynchrone Betriebsaufgabe dar. Eine bestimmte Implementierungsklasse von Callable kann an FutureTask übergeben werden, und es können Vorgänge wie das Warten auf den Erhalt der Ergebnisse dieser asynchronen Vorgangsaufgabe, das Bestimmen, ob sie abgeschlossen wurde, und das Abbrechen der Aufgabe ausgeführt werden. Da FutureTask auch eine Implementierungsklasse der Runnable-Schnittstelle ist, kann FutureTask natürlich auch im Thread-Pool platziert werden.
22. So finden Sie heraus, welcher Thread in der Linux-Umgebung die längste CPU beansprucht
Dies ist eine praktischere Frage, die meiner Meinung nach durchaus sinnvoll ist. Sie können dies tun:
(1) Holen Sie sich die PID, JPS oder PS -EF | des Projekts, dies wurde bereits erwähnt
(2) top -H -p pid, die Reihenfolge kann nicht geändert werden
Dadurch werden das aktuelle Projekt und der Prozentsatz der von jedem Thread beanspruchten CPU-Zeit ausgedruckt. Beachten Sie, dass hier LWP eingegeben wird, also die Thread-Nummer des nativen Threads des Betriebssystems. Mein Laptop hat kein Java-Projekt in der Linux-Umgebung bereitgestellt, daher gibt es keine Möglichkeit, Screenshots zu Demonstrationszwecken zu erstellen Wenn Ihr Unternehmen die Linux-Umgebung zum Bereitstellen von Projekten verwendet, können Sie es einmal ausprobieren.
Mit „top -H -p pid“ + „jps pid“ können Sie leicht den Thread-Stack eines Threads finden, der viel CPU belegt, und so die Ursache für die hohe CPU-Auslastung lokalisieren. Dies ist normalerweise auf fehlerhafte Codeoperationen zurückzuführen das führt zu einer Endlosschleife.
Als letztes ist zu erwähnen, dass der von „top -H -p pid“ gedruckte LWP dezimal ist und die von „jps pid“ gedruckte lokale Thread-Nummer hexadezimal ist. Durch Konvertieren können Sie den Thread lokalisieren, der a belegt viel CPU. Der aktuelle Thread ist gestapelt.
23. Java-Programmierung zum Schreiben eines Programms, das einen Deadlock verursacht
Als ich dieses Thema zum ersten Mal sah, dachte ich, es sei eine sehr gute Frage. Viele Leute wissen, was ein Deadlock ist: Thread A und Thread B warten auf die Sperre des anderen, was dazu führt, dass das Programm auf unbestimmte Zeit in einer Schleife läuft. Natürlich beschränkt es sich darauf. Wenn Sie fragen, wie man ein Deadlock-Programm schreibt, wissen Sie nicht, was ein Deadlock ist. In der Praxis treten Deadlock-Probleme auf, die im Grunde unsichtbar sind.
Um wirklich zu verstehen, was Deadlock ist, ist dieses Problem eigentlich nicht schwierig, nur ein paar Schritte:
(1) Die beiden Threads enthalten zwei Objektobjekte: lock1 und lock2. Diese beiden Sperren dienen als Sperren für synchronisierte Codeblöcke; (2) Der Synchronisationscodeblock in der run()-Methode von Thread 1 erwirbt zuerst die Objektsperre von lock1, Thread.sleep(xxx). Die Zeit muss nicht zu lang sein, 50 Millisekunden reichen fast aus, und dann wird sie erfasst die Objektsperre von lock2. Dies geschieht hauptsächlich, um zu verhindern, dass Thread 1 kontinuierlich die Objektsperren der Objekte lock1 und lock2 erhält
(3) Ausführung von Thread 2) (Der Synchronisationscodeblock in der Methode erhält zuerst die Objektsperre von Sperre2 und dann die Objektsperre von Sperre1. Natürlich wurde zu diesem Zeitpunkt die Objektsperre von Sperre1 von Thread 1 gesperrt , und Thread 2 muss auf den Thread 1
warten, der die Objektsperre von Sperre1 aufhebt Auf diese Weise versucht Thread 1, die Objektsperre von Sperre2 zu erlangen, nachdem Thread 1 „ruht“, und wird blockiert. Ich werde den Code nicht schreiben, da er viel Platz beansprucht. Java Multithreading 7: Deadlock ist in diesem Artikel enthalten, bei dem es sich um die Codeimplementierung der oben genannten Schritte handelt.
24. So wecken Sie einen blockierten Thread
Wenn der Thread aufgrund des Aufrufs der Methode wait(), Sleep() oder Join() blockiert ist, können Sie den Thread unterbrechen und durch Auslösen einer InterruptedException aufwecken Ist ein Betriebssystem implementiert, hat Java-Code keine Möglichkeit, das Betriebssystem direkt zu kontaktieren.
25. Wie helfen unveränderliche Objekte beim Multithreading? Wie bereits erwähnt, stellen unveränderliche Objekte die Speichersichtbarkeit von Objekten sicher. Das Lesen unveränderlicher Objekte erfordert keine zusätzlichen Synchronisierungsmittel, was die Effizienz der Codeausführung verbessert.
26. Was ist Multithread-Kontextwechsel?
Multithread-Kontextwechsel bezieht sich auf den Prozess des Umschaltens der CPU-Steuerung von einem Thread, der bereits ausgeführt wird, auf einen anderen Thread, der bereit ist und darauf wartet, CPU-Ausführungsrechte zu erhalten.
27. Was passiert, wenn die Thread-Pool-Warteschlange voll ist, wenn Sie eine Aufgabe senden?
Wenn Sie LinkedBlockingQueue verwenden, bei dem es sich um eine unbegrenzte Warteschlange handelt, spielt es keine Rolle, weiterhin Aufgaben zur Blockierungswarteschlange hinzuzufügen und auf die Ausführung zu warten, da LinkedBlockingQueue nahezu als unendliche Warteschlange betrachtet werden kann und Aufgaben unbegrenzt speichern kann Eine begrenzte Warteschlange, zum Beispiel Im Fall von ArrayBlockingQueue werden Aufgaben zuerst zur ArrayBlockingQueue hinzugefügt. Wenn die ArrayBlockingQueue voll ist, wird die Ablehnungsrichtlinie RejectedExecutionHandler verwendet, um die vollständigen Aufgaben zu verarbeiten.
28. Welcher Thread-Planungsalgorithmus wird in Java verwendet
Präventiv. Nachdem ein Thread die CPU verbraucht hat, berechnet das Betriebssystem eine Gesamtpriorität basierend auf der Thread-Priorität, dem Thread-Hunger und anderen Daten und weist einem bestimmten Thread die nächste Zeitscheibe zur Ausführung zu.
29. Was ist die Funktion von Thread.sleep(0)
Diese Frage hängt mit der obigen Frage zusammen, daher habe ich sie miteinander verbunden. Da Java einen präventiven Thread-Planungsalgorithmus verwendet, kann es vorkommen, dass ein bestimmter Thread häufig die CPU-Steuerung erhält. Um einigen Threads mit niedrigerer Priorität die CPU-Steuerung zu ermöglichen, können Sie Thread.sleep( 0) verwenden, um einen Vorgang manuell auszulösen Betriebssystem, um Zeitscheiben zuzuweisen, was auch ein Vorgang ist, um die CPU-Steuerung auszugleichen.
30. Was ist Spin
Bei vielen synchronisierten Codes handelt es sich nur um sehr einfachen Code, und die Ausführungszeit ist sehr schnell. Das Sperren aller wartenden Threads ist zu diesem Zeitpunkt möglicherweise kein lohnenswerter Vorgang, da das Blockieren von Threads das Umschalten zwischen Benutzermodus und Kernelmodus erfordert. Da der Code in synchronisiert sehr schnell ausgeführt wird, ist es besser, zu verhindern, dass der Thread, der auf die Sperre wartet, blockiert wird, sondern eine Besetztschleife an der synchronisierten Grenze durchzuführen. Dies ist Spin. Wenn Sie mehrere Besetztschleifen ausführen und feststellen, dass die Sperre nicht erhalten wurde, ist ein erneutes Blockieren möglicherweise die bessere Strategie.
31. Was ist das Java-Speichermodell?
Das Java-Speichermodell definiert eine Spezifikation für den Multithread-Zugriff auf Java-Speicher. Eine vollständige Erklärung des Java-Speichermodells kann nicht in wenigen Sätzen erklärt werden. Lassen Sie mich einige Teile des Java-Speichermodells kurz zusammenfassen:
(1) Das Java-Speichermodell unterteilt den Speicher in Hauptspeicher und Arbeitsspeicher. Der Status der Klasse, also der von den Klassen gemeinsam genutzten Variablen, wird im Hauptspeicher gespeichert. Jedes Mal, wenn der Java-Thread diese Variablen im Hauptspeicher verwendet, liest er die Variablen im Hauptspeicher einmal und lässt sie darin existieren Im Hauptspeicher befindet sich eine Kopie in Ihrem eigenen Arbeitsspeicher. Wenn Sie Ihren eigenen Thread-Code ausführen und diese Variablen verwenden, führen Sie die Kopie in Ihrem eigenen Arbeitsspeicher aus. Nachdem der Thread-Code ausgeführt wurde, wird der neueste Wert im Hauptspeicher aktualisiert
(2) Für Betriebsvariablen im Hauptspeicher und Arbeitsspeicher sind mehrere atomare Operationen definiert
(3) Definieren Sie die Regeln für die Verwendung flüchtiger Variablen
(4) Passiert vor, dh das Prinzip des Vorkommens, definiert einige Regeln, nach denen Operation A vor Operation B ausgeführt werden muss. Beispielsweise muss im selben Thread der Code vor dem Kontrollfluss vor dem Code dahinter auftreten Der Kontrollfluss, eine Freigabesperre Die Entsperraktion muss den nachfolgenden Sperraktionen für dieselbe Sperre usw. vorausgehen. Solange diese Regeln erfüllt sind, sind keine zusätzlichen Synchronisierungsmaßnahmen erforderlich. Wenn ein Code nicht alle Anforderungen erfüllt. Vor Regeln, dann dieser Code Es muss Thread-unsicher sein
32. Was ist CAS
CAS steht für Compare and Swap, also vergleichen und ersetzen. Angenommen, es gibt drei Operanden: Speicherwert V, alter erwarteter Wert A und zu ändernder Wert B. Nur wenn der erwartete Wert A und der Speicherwert V gleich sind, wird der Speicherwert in B geändert und wahr Andernfalls wird „was“ nicht zurückgegeben und „false“ zurückgegeben. Natürlich muss CAS mit flüchtigen Variablen zusammenarbeiten, um sicherzustellen, dass die erhaltene Variable jedes Mal der neueste Wert im Hauptspeicher ist. Andernfalls ist der alte erwartete Wert A für einen bestimmten Thread immer ein unveränderlicher Wert A Wenn der CAS-Vorgang fehlschlägt, wird er niemals erfolgreich sein.
33. Was sind optimistische Sperren und pessimistische Sperren?
(1) Optimistische Sperre: Wie der Name schon sagt, ist sie hinsichtlich der Thread-Sicherheitsprobleme, die durch gleichzeitige Vorgänge verursacht werden, optimistisch und geht davon aus, dass es nicht immer zu Konkurrenz kommen wird. Daher muss sie die Sperre nicht halten und diese vergleichen und ersetzen 2 Als atomare Operation versucht diese Aktion, die Variablen im Speicher zu ändern. Wenn dies fehlschlägt, bedeutet dies, dass ein Konflikt auftritt und eine entsprechende Wiederholungslogik vorhanden sein sollte.
(2) Pessimistische Sperre: Wie der Name schon sagt, ist sie pessimistisch in Bezug auf Thread-Sicherheitsprobleme, die durch gleichzeitige Vorgänge verursacht werden. Die pessimistische Sperre geht davon aus, dass es immer zu Konkurrenz kommt, sodass bei jedem Betrieb einer Ressource eine exklusive Sperre vorliegt Was passiert, ist, dass Sie die Ressource direkt bedienen können, nachdem Sie sie gesperrt haben.
34. Was ist AQS
Lassen Sie uns kurz über AQS sprechen. Der vollständige Name von AQS lautet AbstractQueuedSynchronizer, was als abstrakter Warteschlangensynchronisierer übersetzt werden sollte.
Wenn die Basis von java.util.concurrent CAS ist, ist AQS der Kern des gesamten Java-Parallelitätspakets. ReentrantLock, CountDownLatch, Semaphore usw. verwenden es alle. AQS verbindet tatsächlich alle Einträge in Form einer bidirektionalen Warteschlange, z. B. ReentrantLock. Alle wartenden Threads werden in einem Eintrag platziert und mit einer bidirektionalen Warteschlange verbunden. Wenn der vorherige Thread ReentrantLock verwendet, wird die bidirektionale Warteschlange tatsächlich als erster Eintrag gestartet.
AQS definiert alle Vorgänge in bidirektionalen Warteschlangen, öffnet jedoch nur die Methoden tryLock und tryRelease für Entwickler. Entwickler können die Methoden tryLock und tryRelease entsprechend ihrer eigenen Implementierung umschreiben, um ihre eigenen Parallelitätsfunktionen zu erreichen.
35. Thread-Sicherheit des Singleton-Modus
Das ist eine alltägliche Frage. Die Thread-Sicherheit des Singleton-Modus bedeutet, dass eine Instanz einer bestimmten Klasse in einer Multithread-Umgebung nur einmal erstellt wird. Es gibt viele Möglichkeiten, das Singleton-Muster zu schreiben:
(1) So schreiben Sie das Singleton-Muster im Hungry-Stil: Thread-Sicherheit
(2) So schreiben Sie das Lazy-Singleton-Muster: nicht threadsicher
(3) So schreiben Sie den Double-Check-Lock-Singleton-Modus: Thread-Sicherheit
36. Welche Funktion hat Semaphor?
Semaphor ist ein Semaphor. Seine Funktion besteht darin, die Anzahl der Parallelität eines bestimmten Codeblocks zu begrenzen. Semaphore verfügt über einen Konstruktor, der eine Ganzzahl n übergeben kann, was bedeutet, dass höchstens n Threads auf einen bestimmten Code zugreifen können. Wenn n überschritten wird, warten Sie bitte, bis ein Thread die Ausführung dieses Codeblocks und des nächsten Threads abgeschlossen hat Wieder eintreten. Daraus ist ersichtlich, dass, wenn die im Semaphore-Konstruktor übergebene Ganzzahl vom Typ int n = 1 einer Synchronisierung entspricht.
37. Es gibt nur eine Anweisung „return count“ in der size()-Methode von Hashtable.
Das war eine Verwirrung, die ich vorher hatte. Ich frage mich, ob jemand über dieses Problem nachgedacht hat. Wenn eine Methode mehrere Anweisungen enthält und diese alle mit derselben Klassenvariablen arbeiten, führt das Nichtsperren in einer Multithread-Umgebung unweigerlich zu Thread-Sicherheitsproblemen. Dies ist leicht zu verstehen, aber die Methode size() hat dies eindeutig Nur eine Aussage, warum sollten wir es sperren?
Was dieses Thema betrifft, habe ich während der Arbeit und des Studiums nach und nach Verständnis gewonnen. Es gibt zwei Hauptgründe:
(1) Nur ein Thread kann gleichzeitig die synchronisierte Methode einer festen Klasse ausführen, aber auf die asynchrone Methode der Klasse können mehrere Threads gleichzeitig zugreifen. Daher liegt möglicherweise ein Problem vor. Thread A führt möglicherweise die Put-Methode von Hashtable aus, um Daten hinzuzufügen. Thread B kann die Methode size () normal aufrufen, um die Anzahl der aktuellen Elemente in Hashtable zu lesen, und der gelesene Wert ist möglicherweise nicht vorhanden In der neuesten Version hat Thread A möglicherweise die Daten hinzugefügt, aber Thread B hat die Größe bereits gelesen, ohne size++ zu korrigieren, daher muss die von Thread B gelesene Größe ungenau sein. Nach dem Hinzufügen der Synchronisierung zur size()-Methode bedeutet dies, dass Thread B die size()-Methode erst aufrufen kann, nachdem Thread A den Aufruf der put-Methode abgeschlossen hat, wodurch die Thread-Sicherheit gewährleistet ist
(2) Die CPU führt Code aus, nicht Java-Code. Dies ist sehr wichtig und muss beachtet werden. Java-Code wird schließlich zur Ausführung in Assembler-Code übersetzt. Assembler-Code ist der Code, der tatsächlich mit Hardware-Schaltkreisen interagieren kann. Selbst wenn Sie sehen, dass nur eine Zeile Java-Code vorhanden ist oder dass der nach dem Kompilieren des Java-Codes generierte Bytecode nur eine Zeile ist, bedeutet dies nicht, dass es auf der untersten Ebene nur eine Operation für diese Anweisung gibt . Unter der Annahme, dass der Satz „return count“ zur Ausführung in drei Assembly-Anweisungen übersetzt wird, ist es durchaus möglich, dass der Thread nach der Ausführung des ersten Satzes gewechselt wird.
38. Von welchem Thread werden der Thread-Klassenkonstruktor und der statische Block aufgerufen? Das ist eine sehr knifflige und listige Frage. Bitte denken Sie daran: Die Konstruktionsmethode und der statische Block der Thread-Klasse werden von dem Thread aufgerufen, in dem sich die neue Thread-Klasse befindet, und der Code in der Ausführungsmethode wird vom Thread selbst aufgerufen.
Wenn Sie die obige Aussage verwirrt, lassen Sie mich ein Beispiel geben. Angenommen, Thread1 ist neu in Thread2 und Thread2 ist neu in der Hauptfunktion, dann: (1) Die Konstruktionsmethode und der statische Block von Thread2 werden vom Hauptthread aufgerufen, und die run()-Methode von Thread2 wird von Thread2 selbst aufgerufen (2) Die Konstruktionsmethode und der statische Block von Thread1 werden von Thread2 aufgerufen, und die run()-Methode von Thread1 wird von Thread1 selbst aufgerufen39. Welche Synchronisationsmethode oder welcher Synchronisationsblock ist besser? Synchronisierter Block, was bedeutet, dass der Code außerhalb des synchronisierten Blocks asynchron ausgeführt wird, was die Effizienz des Codes verbessert als die Synchronisierung der gesamten Methode. Bitte kennen Sie einen Grundsatz: Je kleiner der Synchronisierungsumfang, desto besser.
Vor diesem Hintergrund möchte ich erwähnen, dass es zwar umso besser ist, je kleiner der Synchronisierungsbereich ist, es jedoch in der Java Virtual Machine immer noch eine Optimierungsmethode namens Lock Coarsing gibt, die den Synchronisierungsbereich vergrößert. Dies ist nützlich, zum Beispiel ist StringBuffer eine Thread-sichere Klasse. Natürlich ist die am häufigsten verwendete append()-Methode eine Synchronisationsmethode, die wir wiederholt anhängen, was bedeutet, dass sie wiederholt gesperrt wird Dies ist schlecht für die Leistung, da die virtuelle Java-Maschine in diesem Thread wiederholt zwischen dem Kernelmodus und dem Benutzermodus wechseln muss, sodass die virtuelle Java-Maschine den Code verarbeitet, der mehrere Anhängemethoden aufruft. Der Sperrvergröberungsvorgang wird um mehrere erweitert Hängen Sie Operationen an den Anfang und das Ende der Append-Methode an und verwandeln Sie sie in einen großen Synchronisationsblock. Dadurch wird die Anzahl der Sperren und Entsperrungen reduziert und die Codeausführung effektiv verbessert.
40. Wie nutzt man den Thread-Pool für Unternehmen mit hoher Parallelität und kurzer Aufgabenausführungszeit? Wie nutzt man den Thread-Pool für Unternehmen mit geringer Parallelität und langer Aufgabenausführungszeit? Wie nutzt man den Thread-Pool für Unternehmen mit hoher Parallelität und langer Geschäftsausführungszeit?
Dies ist eine Frage, die ich im Internet zur gleichzeitigen Programmierung gesehen habe. Ich hoffe, dass jeder sie sehen und darüber nachdenken kann, denn diese Frage ist sehr gut, sehr praktisch und sehr professionell. Zu diesem Thema ist meine persönliche Meinung:
(1) Für Unternehmen mit hoher Parallelität und kurzer Aufgabenausführungszeit kann die Anzahl der Thread-Pool-Threads auf die Anzahl der CPU-Kerne + 1 eingestellt werden, um den Thread-Kontextwechsel zu reduzieren
(2) Unternehmen mit geringer Parallelität und langer Aufgabenausführungszeit sollten unterschieden werden:
a) Wenn sich die Geschäftszeit über einen längeren Zeitraum auf E/A-Vorgänge konzentriert, d den Thread-Pool, damit die CPU mehr Geschäfte abwickeln kann
b) Wenn sich die Geschäftszeit über einen längeren Zeitraum auf Rechenvorgänge konzentriert, also auf rechenintensive Aufgaben, ist dies nicht dasselbe wie (1). Die Anzahl der Threads im Thread-Pool beträgt kleiner eingestellt, um den Thread-Kontextwechsel zu reduzieren
(3) Hohe Parallelität und lange Geschäftsausführungszeit Der Schlüssel zur Lösung dieser Art von Aufgabe liegt nicht im Thread-Pool, sondern im Design der Gesamtarchitektur. Der erste Schritt besteht darin, zu prüfen, ob bestimmte Daten in diesen Unternehmen zwischengespeichert werden können Das Hinzufügen von Servern ist der dritte Schritt. Informationen zu den Thread-Pool-Einstellungen finden Sie unter (2). Schließlich muss möglicherweise auch das Problem der langen Geschäftsausführungszeit analysiert werden, um festzustellen, ob Middleware zum Aufteilen und Entkoppeln von Aufgaben verwendet werden kann.
Das obige ist der detaillierte Inhalt vonZusammenfassung von 40 Java-Multithreading-Problemen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!