In der Vergangenheit hat Java versucht, präventive begrenzte Interrupts bereitzustellen, es gab jedoch viele Probleme, wie z. B. die zuvor eingeführten aufgegebenen Thread.stop, Thread.suspend und Thread.resume. Andererseits wird aus Rücksicht auf die Robustheit des Java-Anwendungscodes die Programmierschwelle gesenkt und die Wahrscheinlichkeit verringert, dass Programmierer, die den zugrunde liegenden Mechanismus nicht kennen, das System unbeabsichtigt schädigen.
Heutzutage bietet die Thread-Planung von Java keine präventiven Interrupts, sondern verwendet kooperative Interrupts. Tatsächlich ist das Prinzip der kooperativen Unterbrechung sehr einfach, nämlich die Abfrage einer bestimmten Markierung, die eine Unterbrechung anzeigt. Wir können es in jedem gewöhnlichen Code implementieren.
Zum Beispiel der folgende Code:
volatile bool isInterrupted;
//…
while(!isInterrupted) {
berechnen();
}
Die oben genannten Codeprobleme sind jedoch auch offensichtlich. Wenn die Rechenausführungszeit relativ lang ist, kann nicht rechtzeitig auf den Interrupt reagiert werden. Andererseits gibt es durch die Verwendung von Polling zur Überprüfung von Flag-Variablen keine Möglichkeit, Thread-Blockierungsvorgänge wie Warten und Ruhen zu unterbrechen.
Wenn Sie weiterhin die obige Idee verwenden und eine rechtzeitige Reaktion auf den Interrupt wünschen, müssen Sie die Markierungsvariable über die Thread-Planung am unteren Rand der virtuellen Maschine überprüfen. Ja, das geschieht tatsächlich in der JVM.
Das Folgende ist ein Auszug aus dem Quellcode von java.lang.Thread:
öffentlicher statischer boolescher Wert interrupted() {
return currentThread().isInterrupted(true);
}
//…
private native boolean isInterrupted(boolean ClearInterrupted);
Es kann festgestellt werden, dass isInterrupted als native Methode deklariert ist, die von der zugrunde liegenden Implementierung der JVM abhängt.
Tatsächlich verwaltet die JVM intern für jeden Thread ein Interrupt-Flag. Die Anwendung kann jedoch nicht direkt auf diese Interrupt-Variable zugreifen und muss über die folgenden Methoden arbeiten:
öffentlicher Klassenthread {
//Unterbrechungsmarke setzen
public void interrupt() { ... }
//Den Wert der Interrupt-Marke abrufen
public boolean isInterrupted() { ... }
//Löschen Sie die Interrupt-Markierung und geben Sie den Wert der letzten Interrupt-Markierung zurück
public static boolean interrupted() { ... }
}
Normalerweise löst der Aufruf der Interrupt-Methode eines Threads nicht sofort einen Interrupt aus, sondern setzt nur das Interrupt-Flag innerhalb der JVM. Daher kann die Anwendung durch Überprüfen des Interrupt-Flags etwas Besonderes tun oder den Interrupt vollständig ignorieren.
Sie denken vielleicht, dass die JVM, wenn sie nur diesen groben Interrupt-Mechanismus bereitstellt, im Grunde keinen Vorteil gegenüber der anwendungseigenen Methode zum Definieren von Interrupt-Variablen und zum Abfragen hat.
Der Hauptvorteil der internen Interrupt-Variablen der JVM besteht darin, dass sie einen Mechanismus zur Simulation automatischer „Interrupt-Traps“ für bestimmte Situationen bieten.
Beim Ausführen blockierender Aufrufe mit Thread-Planung (z. B. Warten, Schlafen und Beitreten) löst der blockierte Thread im Falle einer Unterbrechung „so schnell wie möglich“ eine InterruptedException aus. Daher können wir das folgende Code-Framework verwenden, um Thread-Blockierungs-Interrupts zu verarbeiten:
versuchen {
//warten, schlafen oder mitmachen
}
Catch(InterruptedException e) {
//Einige Interrupt-Verarbeitungsarbeiten
}
Mit „so schnell wie möglich“ prüft die JVM vermutlich die Interrupt-Variable in der Lücke zwischen der Thread-Planung. Die Geschwindigkeit hängt von der Implementierung der JVM und der Leistung der Hardware ab.
Bei bestimmten Thread-Blockierungsvorgängen löst die JVM jedoch nicht automatisch eine InterruptedException aus. Zum Beispiel bestimmte E/A-Operationen und interne Sperroperationen. Für diese Art von Operation können Interrupts auf andere Weise simuliert werden:
1) Asynchrone Socket-E/A in java.io
Beim Lesen und Schreiben von Sockets blockieren und warten die Lese- und Schreibmethoden von InputStream und OutputStream, reagieren jedoch nicht auf Java-Interrupts. Nach dem Aufruf der Close-Methode des Sockets löst der blockierte Thread jedoch eine SocketException aus.
2) Asynchrone E/A implementiert mit Selector
Wenn der Thread in Selector.select (in java.nio.channels) blockiert ist, führt der Aufruf der Wakeup-Methode zu einer ClosedSelectorException-Ausnahme.
3) Schlosserwerb
Wenn ein Thread darauf wartet, eine interne Sperre zu erhalten, können wir ihn nicht unterbrechen. Mithilfe der lockInterruptably-Methode der Lock-Klasse können wir jedoch Interrupt-Funktionen bereitstellen, während wir auf die Sperre warten.
Darüber hinaus weiß eine Aufgabe in einem Framework, in dem Aufgaben und Threads getrennt sind, normalerweise nicht, von welchem Thread sie aufgerufen wird, und kennt daher nicht die Strategie des aufrufenden Threads zur Behandlung von Unterbrechungen. Daher gibt es keine Garantie dafür, dass die Aufgabe abgebrochen wird, nachdem die Aufgabe das Thread-Unterbrechungsflag gesetzt hat. Daher gibt es zwei Programmierprinzipien:
1) Sie sollten einen Thread nicht unterbrechen, es sei denn, Sie kennen seine Unterbrechungsrichtlinie.
Dieses Prinzip sagt uns, dass wir die Interrupt-Methode eines Threads in einem Framework wie Executer nicht direkt aufrufen sollten, sondern Methoden wie Future.cancel verwenden sollten, um Aufgaben abzubrechen.
2) Der Taskcode sollte nicht erraten, was der Interrupt für den Ausführungsthread bedeutet.
Dieses Prinzip besagt, dass allgemeiner Code, wenn er auf eine InterruptedException trifft, diese nicht abfangen und „verschlucken“ sollte, sondern sie weiterhin an den oberen Code weiterleiten sollte.
Kurz gesagt, der nicht-präventive Interrupt-Mechanismus in Java erfordert, dass wir die traditionelle Idee des präemptiven Interrupts ändern und entsprechende Prinzipien und Muster für die Programmierung übernehmen, basierend auf dem Verständnis seines Wesens.