1..
Die unsichere Klasse im RT.Jar-Paket von JDK bietet Atomvorgänge auf Hardware-Ebene. Die Methoden in unsicher sind alle nativen Methoden, und auf lokale C ++ - Implementierungsbibliotheken werden mithilfe von JNI zugegriffen.
Erläuterung der Hauptfunktionen der unsicheren Klasse in Rt.jar. Die unsichere Klasse bietet atomare Operationen auf Hardware-Ebene und kann Speichervariablen sicher direkt bedienen. Es wird im JUC -Quellcode häufig verwendet. Das Verständnis seiner Prinzipien bildet die Grundlage für das Studium von JUC -Quellcode.
Lassen Sie uns zunächst die Verwendung der Hauptmethoden in der unsicheren Klasse wie folgt verstehen:
1. Langer ObjektFieldOffset (Feldfeld) Methode: Gibt die Speicher -Offset -Adresse der angegebenen Variablen in der Klasse zurück, zu der sie gehört. Die Offset -Adresse wird nur beim Zugriff auf das angegebene Feld in der unsicheren Funktion verwendet. Der folgende Code verwendet unsicher, um den Speicherversatz des variablen Wertes in Atomiclong im Atomiclong -Objekt zu erhalten. Der Code ist wie folgt:
static {try {valueOffset = unafe.ObjectFieldOffset (atomiclong.class.getDeclaredfield ("value")); } catch (Ausnahme ex) {neue Fehler werfen (ex); }}2.Int ArrayBaseOffset (KlassenarrayClass) Methode: Holen Sie sich die Adresse des ersten Elements im Array
3.Int arrayIndexscale (KlassenarrayClass) Methode: Holen Sie sich die Anzahl der Bytes, die von einem einzelnen Element im Array besetzt sind
3.Boolean VergleicheSwaplong (Object OBJ, Long Offset, Long -Afding, Long Update) Methode: Vergleichen Sie, ob der Wert der Variablen des Offset -Offset -Offs -Offs -Offs -Offj gleich zu erwarten ist. Wenn es gleich ist, wird es mit dem Aktualisierungswert aktualisiert und gibt dann True zurück, andernfalls gibt es false zurück.
4.Public Native Long Getlongvolative (Objekt OBJ, Long Offset) Methode: Nehmen Sie den Wert der volatilen Speichersemantik, die der Variablen des Offset -Offs -Offs -Offs in Object OBJ entspricht.
5.Void PutorderedLong (Objekt OBJ, langer Offset, langer Wert) Methode: Legen Sie den Wert des langen Feldes, das der Offset -Offset -Adresse im OBJ -Objekt entspricht, auf den Wert fest. Dies ist die Putlongvolatile -Methode mit Verzögerung und garantiert nicht, dass die Wertveränderung für andere Threads sofort sichtbar ist. Variablen sind nur nützlich, wenn sie mit flüchtigem modifiziert werden und unerwartet modifiziert werden.
6.Void Park (boolean isabsolute, lange Zeit) Methode: Blockieren Sie den aktuellen Faden. Wenn der Parameter isabsolute gleich falsch ist, bedeutet die Zeit, die 0 ist, die ganze Zeit zu blockieren. Die Zeit größer als 0 bedeutet, dass der Blockierungsfaden nach dem Warten auf die angegebene Zeit geweckt wird. Diese Zeit ist ein relativer Wert, ein inkrementeller Wert, dh der aktuelle Thread wird erweckt, nachdem er die Zeit in Bezug auf die aktuelle Zeit angesammelt hat. Wenn Isabsolute gleich wahr ist und die Zeit größer als 0 ist, bedeutet dies, dass es nach dem Blockieren des angegebenen Zeitpunkts geweckt wird. Hier ist die Zeit eine absolute Zeit, die der Wert ist, der zu einem bestimmten Zeitpunkt in MS konvertiert ist. Wenn andere Threads die Interrupt -Methode des aktuellen Blockierungs -Threads aufrufen und den aktuellen Thread unterbrechen, wird auch der aktuelle Thread zurückgegeben. Wenn andere Threads die Unpark -Methode aufrufen und den aktuellen Thread als Parameter nehmen, wird auch der aktuelle Thread zurückgegeben.
7.Void Unpark (Objekt -Thread) Methode: Weck den Blockierfaden nach dem Aufrufen von Park auf, und die Parameter sind die Threads, die aufgeweckt werden müssen.
JDK1.8 wurden mehrere neue Methoden hinzugefügt. Hier finden Sie eine einfache Liste der Methoden zum Betrieb langer Typ wie folgt:
8. Long GetAndSetLong (Objekt OBJ, Long Offset, Long Update) Methode: Nutzen Sie den Wert der variablen volatilen Semantik mit Offset in Object OBJ und setzen Sie den Wert der variablen volatilen Semantik auf Aktualisierung. Die Verwendungsmethode lautet wie folgt:
public Final Long GetandsetLong (Objekt OBJ, Long Offset, Long Update) {long l; do {l = getlongvolatile (obj, offset); // (1)} while (! VergleicheAndswaplong (OBJ, Offset, L, Update)); Rückkehr l; }Aus dem internen Code (1) können Sie GetLongvolative verwenden, um den Wert der aktuellen Variablen zu erhalten, und dann den CAS -Atombetrieb zum Festlegen des neuen Wertes verwenden. Hier berücksichtigt die Verwendung von Loops die Situation, in der mehrere Threads gleichzeitig aufrufen, und dann ist eine Spin-Retry erforderlich, nachdem CAS ausfällt.
9. Die Verwendungsmethode lautet wie folgt:
public Final Long Getandaddlong (Objekt OBJ, langer Offset, langes AddValue) {long l; do {l = getlongvolatile (obj, offset); } while (! VergleicheDSwaplong (OBJ, Offset, L, L + AddValue)); Rückkehr l; }Ähnlich wie bei der Implementierung von GetAndSetLong wird der ursprüngliche Wert + der Wert des übertragenen inkrementellen Parameter -AddValue bei der Verwendung von CAS verwendet.
Wie benutze ich also die unsichere Klasse?
Wenn Sie sehen, dass Unsichere so großartig ist, möchten Sie wirklich üben? Okay, schauen wir uns zuerst den folgenden Code an:
Paket com.hjc; import sun.misc.unsafe;/*** erstellt von cong am 2018/6/6. */public class testunsafe {// Erhalten Sie die Instanz von unsicher (2.2.1) statische endgültige unsichere unsicher = unsicher.getunsafe (); // den Offset -Wert des variablen Zustands in der Klassen testunsafe (2.2.2) statische endgültige lange StateOffset aufzeichnen; // Variable (2.2.3) privates flüchtiger langer Zustand = 0; static {try {// den Offset -Wert der Statusvariablen in der Klasse testunsafe (2.2.4) stateOffset = unafe.ObjectfieldOffset (testunsafe.class.getDeclaredfield ("State")); } catch (Ausnahme ex) {System.out.println (ex.GetLocalizedMessage ()); Neuen Fehler werfen (Ex); }} public static void main (String [] args) {// Erstellen Sie eine Instanz und setzen Sie den Zustandswert auf 1 (2.2.5) testunsafe test = new TestUnSafe (); //(2.2.6) Boolean Sucess = unafe.comPonDswapint (Test, StateOffset, 0, 1); System.out.println (Erfolg); }}Code (2.2.1) erhält eine Instanz von Unsicher, und Code (2.2.3) erstellt einen variablen Zustand, der auf 0 initialisiert wurde.
Der Code (2.2.4) verwendet unsicher.ObjectFieldOffset, um die Speicher -Offset -Adresse der Statusvariablen in der TestUnsafe -Klasse im TestUnsafe -Objekt zu erhalten und in der StateOffset -Variablen zu speichern.
Code (2.2.6) ruft die Vergleichswapt -Methode der erstellten unsicheren Instanz auf und legt den Wert der Zustandsvariablen des Testobjekts fest. Insbesondere wenn die Statusvariable, deren Speicherversatz des Testobjekts StateOffset ist, 0 ist, wird der Aktualisierungswert in 1 geändert.
Wir möchten True in den obigen Code eingeben, aber nach der Ausführung werden die folgenden Ergebnisse ausgegeben:
Warum passiert das? Sie müssen den Code von Getunsafe eingeben, z. B. sehen Sie, was darin getan wird:
privates statisches Finale unsichere theunsafe = new Unafe (); public static unsicherer GetunSafe () {// (2.2.7) Klasse LocalClass = Reflection.getCallerClass (); // (2.2.8) if (! } return theunsafe;} // Beurteilen Sie, ob Paramclassloader ein Bootstrap -Klasse -Loader ist (2.2.9) öffentliches statisches boolean issystemdomainloader (Classloader Paramclassloader) {return paramclassloader == null; }Code (2.2.7) erhält das Klassenobjekt des Objekts, das Getunsafe aufruft, hier ist testunsafe.cals.
Code (2.2.8) bestimmt, ob es sich um die lokale Klasse handelt, die vom Bootstrap -Klassenloader geladen wird. Der Schlüssel hier ist, ob der Bootstrap -Loader testunsafe.class lädt. Diejenigen, die den Klassenlademechanismus der Java Virtual Machine gesehen haben, sehen deutlich, dass die testunsafe.class mit AppClassloader geladen wird, sodass eine Ausnahme direkt hierher ausgelöst wird.
Die Frage ist also, warum Sie dieses Urteil fällen müssen.
Wir wissen, dass die unsichere Klasse in Rt.Jar bereitgestellt wird und die Klasse in Rt.Jar unter Verwendung des Bootstrap -Klassenladers geladen wird. Die Klasse, in der wir die Hauptfunktion starten, wird mit AppClassloader geladen. Wenn Sie also die unsichere Klasse in der Hauptfunktion laden, delegieren Sie den übergeordneten Delegationsmechanismus an Bootstrap, um die unsichere Klasse zu laden.
Wenn es keine Authentifizierung von Code (2.2.8) gibt, kann unsere Anwendung unsicher verwenden, um Dinge nach Belieben zu tun. Die unsichere Klasse kann den Speicher direkt bedienen, was sehr unsicher ist. Daher hat das JDK -Entwicklungsteam diese Einschränkung speziell gemacht und es den Entwicklern nicht ermöglicht, die unsichere Klasse unter regulären Kanälen zu verwenden, aber unsichere Funktionen in der Kernklasse in RT.Jar zu verwenden.
Die Frage ist, was sollten wir tun, wenn wir die unsichere Klasse wirklich instanziieren und die unsichere Funktion verwenden möchten?
Wir sollten die schwarze Technologie der Reflexion nicht vergessen und universelle Reflexion verwenden, um die Instanzmethode von Unsicherheit zu erhalten. Der Code ist wie folgt:
Paket com.hjc; import sun.misc.unsafe; import Java.lang.reflect.field;/*** Erstellt von Cong am 2018/6/6. */public class testunsafe {static final unsicher unsicher; statische endgültige lange StateOffset; privater flüchtiger langer Zustand = 0; static {try {// reflektiert, um die OneSafe -Mitgliedsvariable theunsafe (2.2.10) Feld zu erhalten. // auf Accessable (2.2.11) eingestellt.SetAccessible (true); // Erhalten Sie den Wert dieser Variablen (2.2.12) unsicher = (unsicher) Feld.get (null); // den Staatsversatz in testunsafe (2.2.13) stateOffset = unafe.objectfieldOffset (testunsafe.class.getDeclaredfield ("State")) erhalten; } catch (Ausnahme ex) {System.out.println (ex.GetLocalizedMessage ()); Neuen Fehler werfen (Ex); }} public static void main (String [] args) {TestUnSafe test = new TestUnSafe (); Boolean success = unafe.comPareAndswapint (Test, StateOffset, 0, 1); System.out.println (Erfolg); }}Wenn der obige Code (2.2.10 2.2.11 2.2.12) das Beispiel von unsicherem Ergebnis widerspiegelt, ist das laufende Ergebnis wie folgt:
2. Forschung zum Quellcode der Locksupport -Klasse
Locksupport in Rt.jar in JDK ist eine Werkzeugklasse, und seine Hauptfunktion besteht darin, Threads auszusetzen und aufzuwecken. Es ist die Grundlage für das Erstellen von Schlössern und anderen Synchronisierungsklassen.
Die Locksupport -Klasse wird jedem Thread zugeordnet, der sie verwendet. Der Thread, der standardmäßig die Methode der LockSupport -Klasse aufruft, enthält keine Lizenz. LockSupport wird intern mit der unsicheren Klasse implementiert.
Hier sollten wir auf mehrere wichtige Funktionen von Locksupport achten, wie folgt:
1.Void Park () Methode: Wenn der Thread -Anruf Park () die mit LockSupport verbundene Lizenz erhalten hat, kehrt die Anrufe von locksupport.park () sofort zurück. Andernfalls wird es dem Ruf -Thread verboten, an der Planung des Threads teilzunehmen, dh er wird blockiert und gesperrt. Der folgende Code ist das folgende Beispiel:
Paket com.hjc; import java.util.concurrent.locks.locksupport;/*** Erstellt von Cong am 2018/6/6. */public class lockSupportTest {public static void main (String [] args) {System.out.println ("Park Start!"); Locksupport.park (); System.out.println ("Park Stop!"); }}Wie im obigen Code gezeigt, rufen Sie die Parkmethode direkt in der Hauptfunktion auf, und das Endergebnis gibt nur den Parkstart aus! Anschließend wird der aktuelle Thread ausgesetzt, da der aufrufende Thread standardmäßig keine Lizenz enthält. Die Betriebsergebnisse sind wie folgt:
Wenn Sie andere Threads sehen, rufen Sie die Unpark -Methode (Thread Thread) auf und der aktuelle Thread wird als Parameter verwendet, der Thread, der die Parkmethode aufruft, kehrt zurück. Zusätzlich rufen andere Threads die Interrupt () -Methode des Blockierfadens auf. Wenn das Interrupt-Flag eingestellt ist oder der Blockierungs-Thread nach dem falschen Aufwachen des Threads zurückkehrt, ist es am besten, die Schleifenbedingungen zu verwenden, um zu beurteilen.
Es ist zu beachten, dass der Thread, der die park () blockierte Methode aufruft, von anderen Threads unterbrochen wird und der blockierte Thread -Returns keine InterruptedException -Ausnahme auslöst.
2. METIGE METHE (Thread Thread), wenn ein Thread unpark aufruft, wenn der Parameter -Thread nicht die mit Thread zugeordnete Lizenz und die Locksupport -Klasse enthält, lassen Sie ihn den Thread halten. Wenn der Faden, der Park () namens namens, vorgenommen wird und der Faden aufgerufen wird, wird der Faden erweckt, nachdem Unpark aufgerufen wurde.
Wenn der Thread vorher nicht angerufen wurde, wird die Park () -Methode nach dem Aufrufen der Unpark -Methode sofort zurückgegeben. Der obige Code wird wie folgt geändert:
Paket com.hjc; import java.util.concurrent.locks.locksupport;/*** Erstellt von Cong am 2018/6/6. */public class lockSupportTest {public static void main (String [] args) {System.out.println ("Park Start!"); // den aktuellen Thread den Lizenz locsSupport.unpark (thread.currentThread ()) erhalten; // Park locksupport.park () erneut anrufen; System.out.println ("Park Stop!"); }}Die Betriebsergebnisse sind wie folgt:
Als nächstes suchen wir ein Beispiel, um unser Verständnis von Park zu vertiefen, Unpark, der Code lautet wie folgt:
Importieren Sie java.util.concurrent.locks.locksupport;/*** Erstellt von Cong am 2018/6/6. */public class lockSupportTest {public static void main (String [] args) löst InterruptedException aus {Thread Thread = neuer Thread (neu Runnable () {@Override public void run () {System.out.println ("Child Thread Park Start!"); // Die Park -Thread -Thread -Thread -Threads -Threads -Threads -Threads -Threads -Threads -Threads. }); // Starten Sie den untergeordneten Thread -Thread.start (); // Der Hauptfaden schläft 1S -Thread.Sleep (1000); System.out.println ("Hauptfaden Unpark Start!"); // Rufen Sie Unpark an, um den Thread die Lizenz zu halten, und dann gibt die Parkmethode locksupport.unpark (Thread) zurück. }}Die Betriebsergebnisse sind wie folgt:
Der obige Code erstellt zuerst einen untergeordneten Thread -Thread. Nach dem Start ruft der Kinderfaden die Parkmethode auf. Da der Standard -Child -Thread keine Lizenz besitzt, hängt er selbst auf.
Der Hauptfaden schläft für 1s. Der Zweck ist, dass der Haupt -Thread die Unpark -Methode aufruft und den Kinderfaden den Kinderfadenpark ausgeben lässt! und Block.
Der Haupt -Thread führt dann die Unpark -Methode aus, wobei der Parameter der untergeordnete Thread ist. Der Zweck besteht darin, den untergeordneten Thread die Lizenz zu halten, und dann die vom Child Thread aufgerufene Parkmethode kehrt zurück.
Bei der Rückkehr der Parkmethode wird Ihnen nicht ausgewiesen, welcher Grund sie zurückkehrt. Daher muss der Anrufer erneut prüfen, ob die Bedingung anhand der Parkmethode erfüllt ist, die er im aktuellen Anruf war. Wenn es nicht erfüllt ist, muss er die Parkmethode erneut anrufen.
Zum Beispiel kann der Interrupt -Status eines Threads bei Rückgabe ermittelt werden, ob er zurückgibt, da er basierend auf dem Interrupt -Statusvergleich vor und nach dem Anruf unterbrochen wird.
Um zu veranschaulichen, dass der Thread nach dem Aufrufen der Parkmethode nach dem Unterbrochenen zurückkehrt, ändern Sie den obigen Beispielcode und löschen Sie locksupport.unpark (Thread). und dann thread fügen.interrupt (); Der Code ist wie folgt:
Importieren Sie java.util.concurrent.locks.locksupport;/*** Erstellt von Cong am 2018/6/6. */public class lockSupportTest {public static void main (String [] args) löscht InterruptedException {Thread Thread = neuer Thread (new Runnable () {@Override public void run () {System.out.println ("Subhread Park Start!"). {Locksupport.park (); // Child Thread Thread.start () starten; // Hauptfaden schläft 1s Thread.sleep (1000); System.out.println ("Hauptfaden Unpark Start!"); // Unterbrechung des untergeordneten Thread -Threads.interrupt (); }}Die Betriebsergebnisse sind wie folgt:
Wie der obige Code endet der untergeordnete Thread erst, nachdem der untergeordnete Thread unterbrochen wurde. Wenn der untergeordnete Thread nicht unterbrochen wird, endet der Kinderfaden nicht, wenn Sie Unpark (Thread) nennen.
3.Void Parknanos (Long Nanos) Methode: Ähnlich wie bei Park, wenn der Thread -Calling Park die mit LockSupport verbundene Lizenz erhalten hat, wird sofort zurückgeführt. Der Unterschied besteht darin, dass der Thread, den der Thread nicht erhalten wird, nicht erhalten wird, er suspendiert wird und dann nach der Nanos -Zeit zurückkehrt.
Park unterstützt auch drei Methoden mit Blockerparametern. Wenn ein Thread Park anruft, ohne eine Lizenz zu halten und blockiert und suspendiert wird, wird das Blockerobjekt im Thread aufgezeichnet.
Verwenden Sie diagnostische Werkzeuge, um den Grund zu beobachten, warum der Faden blockiert ist. Die diagnostischen Tools verwenden die GetBlocker -Methode (Thread), um das Blockerobjekt zu erhalten. Daher empfiehlt JDK, dass wir die Parkmethode mit Blockerparametern verwenden und den Blocker darauf einstellen. Wenn die Speicher -Dump -Fehlerbehebung beim Problem zu Problemen zustimmt, können wir wissen, welche Klasse blockiert ist.
Beispiele sind wie folgt:
Importieren Sie java.util.concurrent.locks.locksupport;/*** Erstellt von Cong am 2018/6/6. */public class testpark {public void testpark () {locksupport. Testpark.TestPark (); }}Die Betriebsergebnisse sind wie folgt:
Sie können sehen, dass es Blockierungen läuft. Daher müssen wir die Tools im JDK/Bin -Verzeichnis verwenden, um einen Blick darauf zu werfen. Wenn Sie es nicht wissen, wird empfohlen, zuerst die JVM -Überwachungstools anzusehen.
Wenn Sie JStack PID verwenden, um den Thread -Stack nach dem Laufen anzuzeigen, sind die Ergebnisse, die Sie sehen können, wie folgt:
Dann ändern wir den obigen Code (1) wie folgt:
Locksupport.park (this); // (1)
Führen Sie es erneut aus und verwenden Sie JStack PID, um die Ergebnisse wie folgt anzuzeigen:
Es ist ersichtlich, dass nach der Parkmethode mit Blocker der Thread -Stapel weitere Informationen zum Blockieren von Objekten liefern kann.
Anschließend werden wir den Quellcode der Park -Funktion (Objektblocker) überprüfen. Der Quellcode lautet wie folgt:
public static void Park (Objektblocker) {// den aufrufenden Thread t = thread.currentThread () abrufen; // Setzen Sie den Blocker -Variablen -SetBlocker (t, Blocker); // den Thread unsicher aufhängen (false, 0l); // Löschen Sie die Blockervariable, nachdem der Thread aktiviert wurde, da der Grund normalerweise analysiert wird, wenn der Thread blockiert wird (t, null);};In der flüchtigen Objektblocker der Gewindeklasse befindet sich eine Variable. Es wird verwendet, um das vom Park übergebene Blockerobjekt zu speichern, dh die Blockervariable wird in der Mitgliedsvariable des Threads gespeichert, die die Parkmethode aufruft.
4. Die Funktion void Parknanos (Objektblocker, Long Nanos) hat eine zusätzliche Zeitüberschreitungszeit im Vergleich zu Park (Objektblocker).
5.Void Parkuntil (Objektblocker, lange Frist) Der Quellcode von Parkuntil lautet wie folgt:
public static void parkuntil (Objektblocker, lange Frist) {Thread t = Thread.CurrentThread (); setBlocker (t, Blocker); // isabsolute = true, time = Deadline; bedeutet, dass unsicher.Park (wahr, Frist); setBlocker (t, null); }Sie können sehen, dass es sich um eine festgelegte Frist handelt. Die Zeiteinheit ist Millisekunden, die nach Millisekunden von 1970 bis heute in einen Wert umgewandelt wird. Der Unterschied zwischen diesem und Parknanos (Objektblocker, Long Nanos) besteht darin, dass letztere die wartende Nanos -Zeit aus der aktuellen Zeit berechnet, während der erstere einen Zeitpunkt angibt.
Zum Beispiel müssen wir bis 20:34 Uhr in den Jahren 2018.06.06 warten und dann diesen Zeitpunkt von 1970 bis zu diesem Zeitpunkt in die Gesamtzahl der Millisekunden umwandeln.
Schauen wir uns ein anderes Beispiel an, der Code ist wie folgt:
Import Java.util.queue; Import Java.util.Concurrent.ConcurrentLinkedQueue; Import Java.util.Concurrent.atomic.atomicboolean; Import Java.util.Concurrent.locks.locksupport; */public class fifomutex {private endgültige atomicboolean locked = new atomicboolean (false); Private Final Queue <Thread> Kellner = Neue gleichzeitige LinkedQueue <Thread> (); public void lock () {boolean war unterbrochen = false; Thread current = thread.currentThread (); Kellner.Add (aktuell); // Nur der Faden des Teams des Teams kann das Schloss (1) erhalten (Waiters.peek ()! if (thread.interrupted ()) // (2) wurde unterbrochen = true; } waiters.remove (); if (wurde unterbrochen) // (3) Strom.interrupt (); } public void Unlock () {gesperrt.set (false); Locksupport.unpark (Waiters.peek ()); }}Sie können sehen, dass dies ein Erst-in-First-Out-Schloss ist, dh nur das Warteschlangenheader-Element kann es erhalten. Code (1) Wenn der aktuelle Thread nicht der Warteschlangenheader ist oder die aktuelle Sperre von anderen Threads erfasst wurde, rufen Sie die Parkmethode an, um sich selbst zu hängen.
Dann macht der Code (2) ein Urteil. Wenn die Parkmethode zurückkehrt, weil sie unterbrochen wird, wird der Interrupt ignoriert und das Interrupt -Flag reset und nur eine Flagge erfolgt, und dann stellen Sie erneut fest, ob das aktuelle Thread das Kopfelement der Warteschlange ist oder ob das Schloss von anderen Fäden erfasst wurde. Wenn ja, rufen Sie weiterhin die Parkmethode an, um sich zu hängen.
Wenn die Marke in Code (3) wahr ist, wird der Thread unterbrochen. Wie verstehst du das? Tatsächlich unterbrach andere Threads den Thread. Obwohl ich mich nicht für das Interrupt -Signal interessiere und es ignoriere, bedeutet dies nicht, dass andere Themen nicht an der Flagge interessiert sind, daher muss ich es wiederherstellen.
Zusammenfassen
Das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Referenzwert für das Studium oder die Arbeit eines jeden hat. Wenn Sie Fragen haben, können Sie eine Nachricht zur Kommunikation überlassen. Vielen Dank für Ihre Unterstützung bei Wulin.com.