Zusammenfassung
Diese Serie basiert auf dem Verlauf der Verfeinerung von Zahlen in Gold, und um besser zu lernen, wurde eine Reihe von Aufzeichnungen gemacht. In diesem Artikel wird hauptsächlich vorgestellt: 1. Ideen und Methoden zur Sperroptimierung 2. Sperroptimierung in virtueller Maschine 3. Ein Fall der falschen Verwendung von Schlössern 4. ThreadLocal und deren Quellcodeanalyse
1. Ideen und Methoden der Sperroptimierung
Das Niveau der Parallelität wird in der Einführung in [hohe Parallelität Java 1] erwähnt.
Sobald ein Schloss verwendet wird, bedeutet dies, dass dies blockiert, sodass die Parallelität im Allgemeinen etwas niedriger ist als die lockfreie Situation.
Die hier erwähnte Sperroptimierung bezieht sich darauf, wie die Leistung bei der Blockierung zu schlecht wird. Egal wie Sie es optimieren, die Leistung wird im Allgemeinen etwas schlechter sein als die lock-freie Situation.
Es ist hier zu beachten, dass der in [hohe Genauigkeit Java v] JDK-Parallelitätspaket 1 genannte Trylock in Reentrantlock 1 tendenziell eine lock-freie Methode ist, da es sich nicht hängt, wenn der Trylock-Richter beurteilt wird.
Um die Ideen und Methoden der Sperroptimierung zusammenzufassen, gibt es die folgenden Typen.
1.1 Verringerung der Haltezeit reduzieren
public synchronisierte void syncMethod () {otherCode1 (); mutextMethod (); otherCode2 (); } Wie im obigen Code müssen Sie die Sperre erhalten, bevor Sie die Methode eingeben, und andere Threads müssen draußen warten.
Der Optimierungspunkt hierbei besteht darin, die Wartezeit anderer Threads zu verkürzen, sodass es nur zum Hinzufügen von Sperrungen zu Programmen mit den Anforderungen an die Gewindesicherheit verwendet wird.
public void syncMethod () {otherCode1 (); synchronisiert (this) {mutextMethod (); } otherCode2 (); }1.2 Reduzieren Sie die Verriegelungspartikelgröße
Aufgeteilte große Objekte (auf dieses Objekt kann von vielen Threads zugegriffen werden) in kleine Objekte, wodurch die Parallelität erheblich zunimmt und die Schlosswettbewerb reduziert wird. Nur durch die Reduzierung der Konkurrenz um Schlösser und Vorurteile in Richtung Schlösser wird die Erfolgsrate der leichten Schlösser verbessert.
Der typischste Fall der Verringerung der Granularität der Schloss ist die gleichzeitige. Dies wird in [hohen Parallelität Java v] JDK -Parallelitätspaket 1 erwähnt.
1.3 Sperrtrennung
Die häufigste Sperrtrennung ist die Leseschreiber-Sperre-Readwritelock, die in Leseschreiberschlösser und Schreibschlösser gemäß der Funktion unterteilt ist. Auf diese Weise schließen sich das Lesen und Lesen nicht gegenseitig aus, das Lesen und Schreiben schließen sich gegenseitig aus, was die Sicherheit der Fäden gewährleistet und die Leistung verbessert. Weitere Informationen finden Sie unter [hohe Genauigkeit Java v] JDK -Parallelitätspaket 1.
Die Idee der Trennung von Lesen und Schreiben kann erweitert werden, und solange sich die Operationen nicht auswirken, kann das Schloss getrennt werden.
Zum Beispiel Linked Blockingqueue
Nehmen Sie es aus dem Kopf und stellen Sie die Daten aus dem Schwanz. Natürlich ähnelt es auch dem Arbeitsdiebstahl in Forkjoinpool, der in [hoher Parallelität Java VI] JDK -Parallelitätspaket 2 erwähnt wird.
1.4 Aufaugung sperren
Um im Allgemeinen eine effektive Parallelität zwischen mehreren Threads zu gewährleisten, muss jeder Thread die Sperre so kurz wie möglich halten, dh die Sperre sollte unmittelbar nach der Verwendung öffentlicher Ressourcen freigegeben werden. Nur auf diese Weise können andere Themen, die auf diese Sperre warten, Ressourcen erhalten, um Aufgaben so schnell wie möglich auszuführen. Alles hat jedoch einen Abschluss. Wenn das gleiche Schloss ständig angefordert, synchronisiert und freigegeben wird, wird wertvolle Ressourcen des Systems konsumiert, was der Leistungsoptimierung nicht förderlich ist.
Zum Beispiel:
public void decremethod () {synchronisiert (lock) {// do sth. } // andere unerwünschte Synchronisationsarbeiten durchführen, kann jedoch schnell synchronisiert (Sperre) {// do sth. }} In diesem Fall sollte es gemäß der Idee der Schlossaufrümung zusammengeführt werden
public void deMethodod () {// Integrieren in eine Schlossanforderung Synchronized (Sperre) {// do sth. // andere unerwünschte Synchronisierungsarbeiten erledigen, kann aber schnell ausgeführt werden}} Natürlich gibt es eine Voraussetzung, die Arbeit in der Mitte, für die keine Synchronisation erforderlich ist, wird schnell ausgeführt.
Lassen Sie mich Ihnen ein weiteres extremes Beispiel geben:
für (int i = 0; i <circle; i ++) {synchronisiert (lock) {}} Schlösser müssen in einer Schleife erhalten werden. Obwohl JDK diesen Code intern optimieren wird, ist es besser, ihn direkt zu schreiben
synchronisiert (lock) {für (int i = 0; i <circle; i ++) {}} Wenn es notwendig ist, zu sagen, dass eine solche Schleife zu lang ist und Sie anderen Threads nicht zu lange warten müssen, können Sie sie nur als die oben genannten schreiben. Wenn es keine so ähnlichen Anforderungen gibt, ist es besser, sie direkt in die folgenden zu schreiben.
1.5 Ausscheidung der Sperrung
Die Beseitigung der Sperre ist eine Compiler -Ebene.
Im Instant Compiler kann der Sperrvorgang dieser Objekte beseitigt werden, wenn Objekte, die nicht freigegeben werden können, beseitigt werden.
Vielleicht werden Sie es seltsam finden, dass ich, da auf einige Objekte nicht von mehreren Threads zugegriffen werden können, Sperren hinzufügen sollte? Wäre es nicht besser, beim Schreiben von Code keine Schlösser hinzuzufügen?
Aber manchmal werden diese Schlösser nicht von Programmierern geschrieben. Einige von ihnen haben Schlösser in JDK -Implementierungen wie Klassen wie Vector und StringBuffer. Viele ihrer Methoden haben Schlösser. Wenn wir Methoden dieser Klassen ohne Gewindesicherheit anwenden, wird der Compiler das Schloss entfernen, um die Leistung zu verbessern.
Zum Beispiel:
public static void main (String args []) löscht InterruptedException {long start = system.currentTimemillis () aus; für (int i = 0; i <2000000; i ++) {createStringBuffer ("JVM", "Diagnose"); } long bufferCost = system.currentTimemillis () - start; System.out.println ("craetestringBuffer:" + buffercost + "ms"); } public static String createStringBuffer (String S1, String S2) {StringBuffer sb = new StringBuffer (); SB.Append (S1); Sb.Append (S2); return sb.tostring (); } Der StringBuffer.Append im obigen Code ist ein synchroner Vorgang, aber der StringBuffer ist eine lokale Variable, und die Methode gibt den StringBuffer nicht zurück, sodass es für mehrere Threads nicht möglich ist, darauf zuzugreifen.
Dann ist der Synchronisationsoperation in StringBuffer zu diesem Zeitpunkt bedeutungslos.
Die Sperre -Stornierung wird in den JVM -Parametern festgelegt, muss sie natürlich im Servermodus sein:
-Server -xx:+docapeanalysis -xx:+eliminatelocks
Und die Fluchtanalyse einschalten. Die Funktion der Escape -Analyse besteht darin, festzustellen, ob die Variable wahrscheinlich aus dem Zielfernrohr entkommt.
In der obigen StringBuffer ist beispielsweise die Rückgabe von CraetestringBuffer im obigen Code eine Zeichenfolge, sodass dieser lokale variable StringBuffer nicht an anderer Stelle verwendet wird. Wenn Sie CraetestringBuffer zu ändern
public static StringBuffer craetestringBuffer (String S1, String S2) {StringBuffer sb = new StringBuffer (); SB.Append (S1); Sb.Append (S2); Return SB; } Nachdem dieser StringBuffer zurückgegeben wurde, kann sie überall sonst verwendet werden (z. B. wird die Hauptfunktion das Ergebnis zurückgeben und in Karte usw. einbringen). Anschließend kann die JVM -Fluchtanalyse analysiert werden, dass dieser lokale variable StringBuffer seinem Umfang entgeht.
Basierend auf der Escape -Analyse kann der JVM beurteilen, dass, wenn der lokale variable StringBuffer nicht seinem Umfang entkommt, festgestellt werden kann, dass der StringBuffer nicht von mehreren Threads zugegriffen wird, und dann können diese zusätzlichen Schlösser entfernt werden, um die Leistung zu verbessern.
Wenn die JVM -Parameter sind:
-Server -xx:+docapeanalysis -xx:+eliminatelocks
Ausgabe:
CraetestringBuffer: 302 ms
Die JVM -Parameter sind:
-Server -xx:+docapeanalysis -xx: -eliminatelocks
Ausgabe:
CraetestringBuffer: 660 ms
Offensichtlich ist der Effekt der Sperrausscheidung immer noch sehr offensichtlich.
2. Die Optimierung der Sperroptimierung in virtueller Maschine
Zunächst müssen wir den Objektheader vorstellen. Im JVM hat jedes Objekt einen Objektkopf.
Markieren Sie Wort, Marker für Objektkopf, 32-Bit (32-Bit-System).
Beschreiben Sie den Hash, Sperrinformationen, Müllsammlungstennzeichen, Alter
Es speichert auch einen Zeiger auf den Sperrdatensatz, einen Zeiger auf den Monitor, eine vorgespannte Sperr -Thread -ID usw.
Einfach ausgedrückt, der Objektheader besteht darin, einige systematische Informationen zu speichern.
2.1 Positive Schloss
Die sogenannte Verzerrung ist Exzentrizität, dh das Schloss neigt zu dem Faden, der derzeit das Schloss besitzt.
In den meisten Fällen gibt es keinen Wettbewerb (in den meisten Fällen hat ein Synchronisationsblock nicht mehrere Threads zur gleichen Zeitkonkurrenz), sodass die Leistung durch Verzerrung verbessert werden kann. Das heißt, wenn es keine Konkurrenz gibt und der Thread, der zuvor das Schloss erhalten hat, das Schloss erneut erhält, bestimmt er, ob das Schloss auf mich zeigt, sodass der Thread die Sperre nicht erneut erhalten muss und direkt den Synchronisationsblock eingeben kann.
Die Implementierung der Vorbettungsschloss besteht darin, die Marke der Objektkopfmarke als vorgespannt einzustellen und die Thread -ID in die Objekt -Header -Marke zu schreiben.
Wenn andere Threads dieselbe Sperre anfordern, endet der Vorspannungsmodus
JVM ermöglicht die BIAS -Verriegelung standardmäßig -xx:+UseBiaSedLocking
Im heftigen Wettbewerb erhöht die voreingenommene Verriegelung die Systembelastung (das Urteil, ob es voreingenommen ist, wird jedes Mal hinzugefügt)
Beispiel eines voreingenommenen Schlosses:
Pakettest; import Java.util.List; Import Java.util.Vector; public class Test {public statische Liste <Integer> numberlist = new vector <Integer> (); public static void main (String [] args) löst InterruptedException {long begin = system.currentTimemillis () aus; int count = 0; int startnum = 0; while (count <10000000) {numberList.add (startNum); startnum += 2; zählen ++; } Long End = System.currentTimemillis (); System.out.println (Ende - Beginn); }} Vector ist eine fadensichere Klasse, die den internen Sperrmechanismus verwendet. Jedes Mal wird eine Sperranfrage gestellt. Der obige Code hat nur einen Thread -Haupthaupt und fügt dann wiederholt die Sperranforderung hinzu.
Verwenden Sie die folgenden JVM -Parameter, um das Vorspannungsschloss festzulegen:
-Xx:+useBiaSEdLocking -xx: biasedLockingStartupdelay = 0
BiaSedLockingStartUpDelay bedeutet, dass das Vorspannungsschloss aktiviert ist, nachdem das System einige Sekunden lang beginnt. Die Standardeinstellung beträgt 4 Sekunden, da der allgemeine Datenwettbewerb beim Start des Systems relativ heftig ist. Aktivierte Vorspannungsschlösser zu diesem Zeitpunkt verringern die Leistung.
Seit hier ist die Verzögerungssperrzeit auf 0 gesetzt, um die Leistung der Vorspannungssperrung zu testen.
Zu diesem Zeitpunkt ist die Ausgabe 9209
Schalten Sie die Vorspannungsschloss unten aus:
-Xx: -UnebiaSedLocking
Die Ausgabe ist 9627
Wenn es keine Konkurrenz gibt, wird die Leistung der Ermöglichung von Verzerrungsschlössern im Allgemeinen um etwa 5%verbessert.
2.2 Leichtes Schloss
Die Sicherheit von Java wird basierend auf dem Sperrmechanismus implementiert, und die Leistung von Sperre ist oft nicht zufriedenstellend.
Der Grund dafür ist, dass Monitorexit, zwei Bytecode -Primitive, die die Multithread -Synchronisation steuern, von JVM implementiert werden.
Mutex ist eine relativ ressourcenverbrauchende Operation, die dazu führt, dass der Faden hängt und in kurzer Zeit zum ursprünglichen Faden verschoben werden muss.
Um den Schließmechanismus von Java zu optimieren, wird seit Java6 das Konzept des Leichtgewichts eingeführt.
Leichte Verriegelung soll die Wahrscheinlichkeit eines Multi-Threading-Eingangs von Mutex verringern, nicht um Mutex zu ersetzen.
Es verwendet die CPU Primitive Compare-and-Swap (CAS, Assembleranweisung CMPXCHG) und versucht, vor dem Betreten des Mutex zu beheben.
Wenn die Vorbettungsschloss ausfällt, führt das System einen leichten Sperrvorgang durch. Der Zweck seiner Existenz besteht darin, die Verwendung von Mutex auf Betriebssystemebene so weit wie möglich zu vermeiden, da diese Leistung relativ schlecht sein wird. Da JVM selbst eine Anwendung ist, hoffe ich, das Problem der Threadsynchronisierung auf Anwendungsebene zu lösen.
Zusammenfassend lässt sich sagen, dass Leichtes Schloss eine schnelle Verriegelungsmethode ist. Verwenden Sie vor dem Eingeben von Mutex CAS -Operationen, um Schlösser hinzuzufügen. Versuchen Sie, MUTEX nicht auf Betriebssystemebene zu verwenden, um die Leistung zu verbessern.
Wenn dann die Vorspannungsschloss ausfällt, sind die Schritte des leichten Schlosses:
1. Speichern Sie den Markzier des Objektkopfs in das gesperrte Objekt (das Objekt hier bezieht sich auf das gesperrte Objekt, z. B. synchronisiert (this) {}, dies ist das Objekt hier).
lock-> set_displaced_header (mark);
2. Stellen Sie den Objektheader als Zeiger auf das Schloss (im Thread -Stapelraum).
if (mark == (markoop) atomic :: cmpxchg_ptr (lock, obj ()-> mark_addr (), mark)) {uVent (Slow_enter: Release StackLock); zurückkehren ; } Das Schloss befindet sich im Fadenstapel. Um festzustellen, ob ein Faden dieses Schloss hält, bestimmen Sie einfach, ob sich der von dem Objektkopf gerichtete Raum im Adressraum des Fadenstapels befindet.
Wenn das leichte Schloss fehlschlägt, bedeutet dies, dass Konkurrenz und Upgrade auf ein Schwergewichtsschloss (reguläres Sperre) vorhanden sind, bei dem die Synchronisationsmethode auf Betriebssystemebene ist. In Ermangelung eines Schlosswettbewerbs verringern leichte Schlösser den Leistungsverlust, der durch herkömmliche Schlösser unter Verwendung von OS -Mutexes verursacht wird. Wenn die Konkurrenz sehr heftig ist (leichte Schlösser immer scheitern), können leichte Schlösser viel zusätzliche Vorgänge ausführen, was zu einer Leistungsverschlechterung führt.
2.3 Spin -Lock
Wenn der Wettbewerb besteht, da der Versuch des leichten Schlosses fehlschlägt, kann er direkt auf ein Schwergewichtsschloss aufgerüstet werden, um den gegenseitigen Ausschluss des Betriebssystemebens zu verwenden. Es ist auch möglich, das Spinschloss erneut auszuprobieren.
Wenn der Faden das Schloss schnell erhalten kann, können Sie den Faden nicht an der Betriebssystemschicht hängen, den Faden mehrere leere Operationen (Spination) ausführen und ständig versuchen, das Schloss (ähnlich wie Trylock) zu erhalten. Natürlich ist die Anzahl der Schleifen begrenzt. Wenn die Anzahl der Schleifen erreicht ist, wird sie weiterhin auf ein Schwergewichtsschloss verbessert. Wenn jeder Faden nur wenig Zeit hat, um das Schloss zu halten, kann das Spin -Sperre versuchen, zu vermeiden, dass Fäden an der Betriebssystemschicht suspendiert werden.
Jdk1.6 -xx:+usespinning ist aktiviert
Entfernen Sie in JDK1.7 diesen Parameter und ändern Sie ihn in eine integrierte Implementierung.
Wenn der Synchronisationsblock sehr lang ist und der Spin fehlschlägt, wird die Systemleistung abgebaut. Wenn der Synchronisationsblock sehr kurz ist und der Spin erfolgreich ist, spart er die Schaltzeit der Gewinde und verbessert die Systemleistung.
2.4 Positive Schloss, Leichte Schloss, Spin -Lock -Zusammenfassung
Das obige Schloss ist keine Java-Sperroptimierungsmethode auf Java-Ebene, sondern ist in die JVM eingebaut.
Zunächst einmal ist es, den Leistungsverbrauch eines Fadens zu vermeiden, wenn es wiederholt das gleiche Schloss erfasst/freigibt. Wenn derselbe Thread dieses Sperre immer noch erwirbt, wird direkt in den Synchronisationsblock eingetragen, wenn versucht wird, die Schlösser zu verzerrt, und es müssen nicht erneut das Schloss erhalten werden.
Leichte Schlösser und Spinschlösser sollen direkte Aufrufe von MUTEX-Operationen auf Betriebssystemebene vermeiden, da das Auslegen von Threads ein sehr ressourcenverbrauchender Betrieb ist.
Um die Verwendung von Schwergewichtsschlössern (Mutex auf Betriebssystemebene) zu vermeiden, werden wir zunächst ein leichtes Schloss ausprobieren. Das leichte Schloss versucht, den CAS -Betrieb zu verwenden, um das Schloss zu erhalten. Wenn das leichte Schloss nicht erhalten kann, bedeutet dies, dass es Konkurrenz gibt. Aber vielleicht erhalten Sie bald das Schloss und versuchen Spinschlösser, machen Sie ein paar leere Schleifen auf dem Faden und versuchen Sie, die Schlösser jedes Mal zu erhalten, wenn Sie Schleifen haben. Wenn auch das Spinschloss fehlschlägt, kann es nur auf ein Schwergewichtsschloss verbessert werden.
Es ist ersichtlich, dass voreingenommene Schlösser, leichte Schlösser und Spinschlösser optimistische Schlösser sind.
3.. Ein Fall der falschen Verwendung von Schlössern
Public Class Integerlock {statische Ganzzahl i = 0; public static class addthread erweitert thread {public void run () {für (int k = 0; k <100000; k ++) {synchronized (i) {i ++; }}}} public static void main (String [] args) löst InterruptedException aus {addthread t1 = new addthread (); Addthread t2 = neu addthread (); t1.start (); t2.Start (); t1.join (); t2.join (); System.out.println (i); }} Ein sehr grundlegender Fehler ist, dass in dem [hohen Parallelität Java VII.] Parallelitätsmustern das Interger endgültig unverändert ist, und nach jedem ++ wird ein neues Interger generiert und I zugewiesen, sodass die Schlösser zwischen den beiden Threads unterschiedlich sind. Es ist also nicht fadensicher.
4. Threadlocal und seine Quellcodeanalyse
Es mag ein bisschen unangemessen sein, ThreadLocal hier zu erwähnen, aber ThreadLocal ist eine Möglichkeit, Schlösser zu ersetzen. Es ist also immer noch notwendig, es zu erwähnen.
Die Grundidee ist, dass in einem Multi-Thread Datenkonflikte gesperrt werden müssen. Wenn ThreadLocal verwendet wird, wird für jeden Thread eine Objektinstanz bereitgestellt. Verschiedene Themen zugreifen nur auf ihre eigenen Objekte, nicht auf andere Objekte. Auf diese Weise müssen das Schloss nicht existieren.
Pakettest; import Java.Text.ParseException; Import Java.Text.SimpledateFormat; Import Java.util.date; Import Java.util.Concurrent.executorService; HH: MM: SS "); public statische Klasse ParseDate implementiert runnable {int i = 0; public ParseDate (int i) {this.i = i; } public void run () {try {Datum t = Sdf.Parse ("2016-02-16 17:00:" + i % 60); System.out.println (i + ":" + t); } catch (parseException e) {e.printstacktrace (); }}} public static void main (String [] args) {ExecutorService es = Executors.NewFixedThreadpool (10); für (int i = 0; i <1000; i ++) {es.execute (neuer ParseDate (i)); }}} Da SimpleDateFormat nicht mit Thread-Sicherheit ist, wird der obige Code falsch verwendet. Der einfachste Weg ist es, eine Klasse selbst zu definieren und sie mit synchronisiertem (ähnlich wie bei synchronisiertenMap) einzuwickeln. Dies führt zu Problemen bei hoher Parallelität. Die Behauptung von synchronisierten Ergebnissen führt dazu, dass nur ein Thread jeweils eintritt, und das Parallelitätsvolumen ist sehr niedrig.
Dieses Problem wird gelöst, indem ThreadLocal verwendet wird, um SimpledateFormat zu verringern.
Pakettest; import Java.Text.ParseException; import Java.Text.SimpledateFormat; Import Java.util.date; import Java.util.concurrent.executorService; public statische Klasse ParseDate implementiert runnable {int i = 0; public ParseDate (int i) {this.i = i; } public void run () {try {if (tl.get () == null) {tl.set (new SimpledateFormat ("yyyy-mm-dd hh: mm: ss"); } Datum t = tl.get (). Parse ("2016-02-16 17:00:" + i % 60); System.out.println (i + ":" + t); } catch (parseException e) {e.printstacktrace (); }}} public static void main (String [] args) {ExecutorService es = Executors.NewFixedThreadpool (10); für (int i = 0; i <1000; i ++) {es.execute (neuer ParseDate (i)); }}}Wenn jeder Thread ausgeführt wird, wird festgelegt, ob der aktuelle Thread ein SimpledateFormat -Objekt hat.
if (tl.get () == null)
Wenn nicht, wird New SimpledateFormat an den aktuellen Thread gebunden
tl.set (new SimpledateFormat ("yyyy-mm-dd hh: mm: ss");
Verwenden Sie dann den SimpledateFormat des aktuellen Threads, um zu analysieren
tl.get (). parse ("2016-02-16 17:00:" + i % 60);
Im Anfangscode gab es nur einen SimpledateFormat, der ThreadLocal verwendete, und ein SimpleDateFormat war für jeden Thread neu.
Es ist zu beachten, dass Sie hier nicht jeder ThreadLocal eine öffentliche SimpledateFormat festlegen sollten, da dies nutzlos ist. Jeder muss einem SimpledateFormat neu vergeben werden.
In Hibernate gibt es typische Anwendungen für ThreadLocal.
Schauen wir uns die Quellcode -Implementierung von ThreadLocal an
Zunächst gibt es in der Thread -Klasse eine Mitgliedsvariable:
ThreadLocal.ThreadLocalMap ThreadLocals = null;
Und diese Karte ist der Schlüssel zur Implementierung von ThreadLocal
public void set (t value) {thread t = thread.currentThread (); ThreadLocalMap map = getMap (t); if (map! = null) map.set (this, Wert); sonst CreateMap (t, Wert); } Gemäß ThreadLocal können Sie den entsprechenden Wert einstellen und abrufen.
Die ThreadLocalMap -Implementierung hier ähnelt HashMap, aber es gibt Unterschiede im Umgang mit Hash -Konflikten.
Wenn ein Hash -Konflikt in ThreadLocalMap auftritt, ist es nicht wie HashMap, verknüpfte Listen zu verwenden, um den Konflikt zu beheben, sondern den Index ++ in den nächsten Index zu setzen, um den Konflikt zu beheben.