java synchronized what is thread synchronization java
Dieses Tutorial erklärt die Thread-Synchronisation in Java zusammen mit verwandten Konzepten wie Java Lock, Race Condition, Mutex, Java Volatile und Deadlock in Java:
In einer Multithreading-Umgebung, in der mehrere Threads beteiligt sind, kann es zu Konflikten kommen, wenn mehr als ein Thread gleichzeitig versucht, dieselbe Ressource abzurufen. Diese Zusammenstöße führen zu „Rennbedingungen“ und somit führt das Programm zu unerwarteten Ergebnissen.
Beispielsweise, Eine einzelne Datei wird von zwei Threads aktualisiert. Wenn ein Thread T1 gerade diese Datei aktualisiert, sagen Sie eine Variable. Nehmen wir an, der zweite Thread T2 aktualisiert dieselbe Variable ebenfalls, während dieses Update von T1 noch läuft. Auf diese Weise liefert die Variable falsche Ergebnisse.
=> Sehen Sie sich hier die komplette Java-Schulungsserie an.
Wenn mehrere Threads beteiligt sind, sollten wir diese Threads so verwalten, dass jeweils nur ein Thread auf eine Ressource zugreifen kann. Im obigen Beispiel sollte die Datei, auf die beide Threads zugreifen, so verwaltet werden, dass T2 erst dann auf die Datei zugreifen kann, wenn T1 auf sie zugegriffen hat.
Dies geschieht in Java mit „ Thread-Synchronisation ”.
Was du lernen wirst:
- Thread-Synchronisation in Java
- Multithreading ohne Synchronisation
- Multithreading mit Synchronisation
- Fazit
Thread-Synchronisation in Java
Da Java eine Sprache mit mehreren Threads ist, ist die Thread-Synchronisierung in Java von großer Bedeutung, da mehrere Threads in einer Anwendung parallel ausgeführt werden.
Wir verwenden Schlüsselwörter 'Synchronisiert' und 'Flüchtig' Synchronisation in Java zu erreichen
Wir benötigen eine Synchronisierung, wenn das gemeinsam genutzte Objekt oder die gemeinsam genutzte Ressource veränderbar ist. Wenn die Ressource unveränderlich ist, lesen die Threads die Ressource nur gleichzeitig oder einzeln.
In diesem Fall müssen wir die Ressource nicht synchronisieren. In diesem Fall stellt JVM dies sicher Java-synchronisierter Code wird jeweils von einem Thread ausgeführt .
In den meisten Fällen kann der gleichzeitige Zugriff auf gemeinsam genutzte Ressourcen in Java zu Fehlern wie 'Speicherinkonsistenz' und 'Thread-Interferenz' führen. Um diese Fehler zu vermeiden, müssen gemeinsam genutzte Ressourcen synchronisiert werden, damit sich der Zugriff auf diese Ressourcen gegenseitig ausschließt.
Wir verwenden ein Konzept namens Monitore zur Implementierung der Synchronisation. Auf einen Monitor kann jeweils nur ein Thread zugreifen. Wenn ein Thread die Sperre erhält, können wir sagen, dass der Thread den Monitor betreten hat.
Wenn ein bestimmter Thread auf einen Monitor zugreift, wird der Monitor gesperrt und alle anderen Threads, die versuchen, in den Monitor einzutreten, werden angehalten, bis der Zugriffsthread beendet ist und die Sperre aufhebt.
In diesem Tutorial werden wir die Synchronisation in Java ausführlich behandeln. Lassen Sie uns nun einige grundlegende Konzepte zur Synchronisation in Java diskutieren.
Rennbedingung In Java
Wenn in einer Multithread-Umgebung mehr als ein Thread versucht, gleichzeitig auf eine gemeinsam genutzte Ressource zum Schreiben zuzugreifen, treten mehrere Threads gegeneinander an, um den Zugriff auf die Ressource zu beenden. Dies führt zu einer „Rennbedingung“.
Eine zu berücksichtigende Sache ist, dass es kein Problem gibt, wenn mehrere Threads versuchen, nur zum Lesen auf eine gemeinsam genutzte Ressource zuzugreifen. Das Problem tritt auf, wenn mehrere Threads gleichzeitig auf dieselbe Ressource zugreifen.
Race-Bedingungen treten auf, weil die Threads im Programm nicht richtig synchronisiert sind. Wenn wir die Threads ordnungsgemäß synchronisieren, sodass jeweils nur ein Thread auf die Ressource zugreift und die Race-Bedingung nicht mehr besteht.
Wie erkennen wir den Rennzustand?
Der beste Weg, um den Rennzustand zu erkennen, ist die Codeüberprüfung. Als Programmierer sollten wir den Code gründlich überprüfen, um mögliche Rennbedingungen festzustellen.
Sperren / Monitore in Java
Wir haben bereits erwähnt, dass wir Monitore oder Sperren verwenden, um die Synchronisation zu implementieren. Der Monitor oder die Sperre ist eine interne Entität und jedem Objekt zugeordnet. Wenn ein Thread auf das Objekt zugreifen muss, muss er zuerst die Sperre oder den Monitor seines Objekts abrufen, am Objekt arbeiten und dann die Sperre aufheben.
Sperren in Java sehen wie folgt aus:
public class Lock { private boolean isLocked = false; public synchronized void lock() throws InterruptedException { while(isLocked) { wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } }
Wie oben gezeigt, haben wir eine lock () -Methode, die die Instanz sperrt. Alle Threads, die die lock () -Methode aufrufen, werden blockiert, bis die unblock () -Methodensätze auf false gesetzt sind und alle wartenden Threads benachrichtigen.
Einige wichtige Hinweise zu Sperren:
- In Java verfügt jedes Objekt über eine Sperre oder einen Monitor. Auf diese Sperre kann ein Thread zugreifen.
- Es kann jeweils nur ein Thread diesen Monitor oder diese Sperre erfassen.
- Die Programmiersprache Java bietet ein Schlüsselwort 'Synchronisiert', mit dem wir die Threads synchronisieren können, indem wir einen Block oder eine Methode als 'Synchronisiert' festlegen.
- Die gemeinsam genutzten Ressourcen, auf die die Threads zugreifen müssen, werden unter diesem synchronisierten Block / dieser synchronisierten Methode gespeichert.
Mutexe in Java
Wir haben bereits besprochen, dass in einer Multithread-Umgebung Race-Bedingungen auftreten können, wenn mehr als ein Thread gleichzeitig versucht, auf die gemeinsam genutzten Ressourcen zuzugreifen, und die Race-Bedingungen zu unerwarteten Ausgaben führen.
Der Teil des Programms, der versucht, auf die gemeinsam genutzte Ressource zuzugreifen, wird als bezeichnet 'Kritischer Abschnitt' . Um das Auftreten von Rennbedingungen zu vermeiden, muss der Zugriff auf den kritischen Abschnitt synchronisiert werden. Durch die Synchronisierung dieses kritischen Abschnitts stellen wir sicher, dass jeweils nur ein Thread auf den kritischen Abschnitt zugreifen kann.
Der einfachste Synchronisationstyp ist der „Mutex“. Mutex stellt sicher, dass zu einem bestimmten Zeitpunkt nur ein Thread den kritischen Abschnitt ausführen kann.
Der Mutex ähnelt dem oben diskutierten Konzept von Monitoren oder Schlössern. Wenn ein Thread auf einen kritischen Abschnitt zugreifen muss, muss er den Mutex abrufen. Sobald der Mutex erfasst wurde, greift der Thread auf den Code des kritischen Abschnitts zu und gibt den Mutex frei, wenn er fertig ist.
Die anderen Threads, die auf den Zugriff auf den kritischen Abschnitt warten, werden in der Zwischenzeit blockiert. Sobald der Thread, der Mutex enthält, ihn freigibt, wird ein anderer Thread in den kritischen Bereich eintreten.
grundlegende SQL-Interview Fragen und Antworten für Erstsemester pdf
Es gibt verschiedene Möglichkeiten, wie wir einen Mutex in Java implementieren können.
- Verwenden des synchronisierten Schlüsselworts
- Semaphor verwenden
- Verwenden von ReentrantLock
In diesem Tutorial werden wir den ersten Ansatz diskutieren, d. H. Die Synchronisation. Die beiden anderen Ansätze - Semaphore und ReentrantLock - werden im nächsten Tutorial erläutert, in dem das gleichzeitige Java-Paket erläutert wird.
Synchronisiertes Schlüsselwort
Java bietet ein Schlüsselwort 'Synchronisiert', das in einem Programm zum Markieren eines kritischen Abschnitts verwendet werden kann. Der kritische Abschnitt kann ein Codeblock oder eine vollständige Methode sein. Somit kann nur ein Thread auf den kritischen Abschnitt zugreifen, der durch das Schlüsselwort Synchronized gekennzeichnet ist.
Wir können die gleichzeitigen Teile (Teile, die gleichzeitig ausgeführt werden) für eine Anwendung mit dem Schlüsselwort Synchronized schreiben. Wir werden auch die Rennbedingungen los, indem wir einen Codeblock oder eine Methode synchronisieren.
Wenn wir einen Block oder eine Methode synchronisiert markieren, schützen wir die gemeinsam genutzten Ressourcen in diesen Entitäten vor gleichzeitigem Zugriff und damit vor Beschädigung.
Arten der Synchronisation
Es gibt zwei Arten der Synchronisation, wie unten erläutert:
# 1) Prozesssynchronisation
Bei der Prozesssynchronisierung werden mehrere Prozesse oder Threads gleichzeitig ausgeführt. Sie erreichen letztendlich einen Zustand, in dem diese Prozesse oder Threads eine bestimmte Abfolge von Aktionen festlegen.
# 2) Thread-Synchronisation
Bei der Thread-Synchronisierung versucht mehr als ein Thread, auf einen gemeinsam genutzten Bereich zuzugreifen. Die Threads werden so synchronisiert, dass jeweils nur ein Thread auf den gemeinsam genutzten Speicherplatz zugreift.
Die Prozesssynchronisierung ist nicht Gegenstand dieses Lernprogramms. Daher werden wir hier nur die Thread-Synchronisation diskutieren.
In Java können wir das synchronisierte Schlüsselwort verwenden mit:
- Ein Codeblock
- Eine Methode
Die oben genannten Typen schließen sich gegenseitig aus. Der gegenseitige Ausschluss verhindert, dass sich die Threads, die auf gemeinsam genutzte Daten zugreifen, gegenseitig stören.
Die andere Art der Thread-Synchronisation ist die „InterThread-Kommunikation“, die auf der Zusammenarbeit zwischen Threads basiert. Die Interthread-Kommunikation ist nicht Gegenstand dieses Lernprogramms.
Bevor wir mit der Synchronisation von Blöcken und Methoden fortfahren, implementieren wir ein Java-Programm, um das Verhalten von Threads zu demonstrieren, wenn keine Synchronisation stattfindet.
Multithreading ohne Synchronisation
Das folgende Java-Programm verfügt über mehrere Threads, die nicht synchronisiert sind.
class PrintCount { //method to print the thread counter public void printcounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run method for thread public void run() { PD.printcounter(); System.out.println('Thread ' + threadName + ' exiting.'); } //start method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args[]) { PrintCount PD = new PrintCount(); //create two instances of thread class ThreadCounter T1 = new ThreadCounter( 'ThreadCounter_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'ThreadCounter_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }
Ausgabe
An der Ausgabe können wir erkennen, dass die Ausgabe inkonsistent ist, da die Threads nicht synchronisiert sind. Beide Threads starten und zeigen dann nacheinander den Zähler an. Beide Threads werden am Ende beendet.
Aus dem angegebenen Programm sollte der erste Thread nach dem Anzeigen der Zählerwerte beendet worden sein, und dann sollte der zweite Thread begonnen haben, die Zählerwerte anzuzeigen.
Gehen wir nun zur Synchronisation und beginnen mit der Synchronisation der Codeblöcke.
Synchronisierter Codeblock
Ein synchronisierter Block wird verwendet, um einen Codeblock zu synchronisieren. Dieser Block besteht normalerweise aus wenigen Zeilen. Ein synchronisierter Block wird verwendet, wenn nicht die gesamte Methode synchronisiert werden soll.
Beispielsweise, Wir haben eine Methode mit beispielsweise 75 Codezeilen. Davon müssen jeweils nur 10 Codezeilen von jeweils einem Thread ausgeführt werden. In diesem Fall wird das System belastet, wenn die gesamte Methode synchronisiert wird. In solchen Situationen entscheiden wir uns für synchronisierte Blöcke.
Der Umfang der synchronisierten Methode ist immer kleiner als der einer synchronisierten Methode. Eine synchronisierte Methode sperrt ein Objekt einer gemeinsam genutzten Ressource, die von mehreren Threads verwendet werden soll.
Die allgemeine Syntax eines synchronisierten Blocks lautet wie folgt:
synchronized (lock_object){ //synchronized code statements }
Hier ist 'lock_object' ein Objektreferenzausdruck, für den die Sperre erhalten werden soll. Wenn ein Thread zur Ausführung auf die synchronisierten Anweisungen im Block zugreifen möchte, muss er die Sperre auf dem Monitor 'lock_object' erwerben.
Wie bereits erläutert, stellt das synchronisierte Schlüsselwort sicher, dass jeweils nur ein Thread eine Sperre erhalten kann und alle anderen Threads warten müssen, bis der Thread, der die Sperre hält, beendet ist und die Sperre aufhebt.
Hinweis
- Eine 'NullPointerException' wird ausgelöst, wenn das verwendete lock_object Null ist.
- Wenn ein Thread schläft, während er die Sperre hält, wird die Sperre nicht freigegeben. Die anderen Threads können während dieser Ruhezeit nicht auf das freigegebene Objekt zugreifen.
Nun werden wir das obige Beispiel vorstellen, das bereits mit geringfügigen Änderungen implementiert wurde. Im früheren Programm haben wir den Code nicht synchronisiert. Jetzt werden wir den synchronisierten Block verwenden und die Ausgabe vergleichen.
Multithreading mit Synchronisation
Im folgenden Java-Programm verwenden wir einen synchronisierten Block. Bei der Ausführungsmethode synchronisieren wir den Code der Zeilen, die den Zähler für jeden Thread drucken.
class PrintCount { //print thread counter public void printCounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run () method for thread with synchronized block public void run() { synchronized(PD) { PD.printCounter(); } System.out.println('Thread ' + threadName + ' exiting.'); } //start () method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args[]) { PrintCount PD = new PrintCount(); //create thread instances ThreadCounter T1 = new ThreadCounter( 'Thread_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'Thread_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }
Ausgabe
Jetzt ist die Ausgabe dieses Programms unter Verwendung eines synchronisierten Blocks ziemlich konsistent. Wie erwartet werden beide Threads ausgeführt. Der erste Thread hat die Anzeige der Zählerwerte beendet und beendet. Dann zeigt der zweite Thread die Zählerwerte an und wird beendet.
Synchronisierte Methode
Lassen Sie uns die synchronisierte Methode in diesem Abschnitt diskutieren. Früher haben wir gesehen, dass wir einen kleinen Block, der aus weniger Codezeilen besteht, als synchronisierten Block deklarieren können. Wenn die gesamte Funktion synchronisiert werden soll, können wir eine Methode als synchronisiert deklarieren.
Datenmaskierungs-Tools für SQL Server
Wenn eine Methode synchronisiert wird, kann jeweils nur ein Thread einen Methodenaufruf ausführen.
Die allgemeine Syntax zum Schreiben einer synchronisierten Methode lautet:
synchronized method_name (parameters){ //synchronized code }
Genau wie bei einem synchronisierten Block benötigen wir bei einer synchronisierten Methode ein lock_object, das von Threads verwendet wird, die auf die synchronisierte Methode zugreifen.
Bei der synchronisierten Methode kann das Sperrobjekt eines der folgenden sein:
- Wenn die synchronisierte Methode statisch ist, wird das Sperrobjekt durch das Objekt '.class' angegeben.
- Bei einer nicht statischen Methode wird das Sperrobjekt durch das aktuelle Objekt angegeben, d. H. 'Dieses' Objekt.
Eine Besonderheit des synchronisierten Schlüsselworts besteht darin, dass es erneut eingegeben wird. Dies bedeutet, dass eine synchronisierte Methode eine andere synchronisierte Methode mit derselben Sperre aufrufen kann. Ein Thread, der die Sperre hält, kann also auf eine andere synchronisierte Methode zugreifen, ohne eine andere Sperre erwerben zu müssen.
Die synchronisierte Methode wird anhand des folgenden Beispiels demonstriert.
class NumberClass { //synchronized method to print squares of numbers synchronized void printSquares(int n) throws InterruptedException { //iterate from 1 to given number and print the squares at each iteration for (int i = 1; i <= n; i++) { System.out.println(Thread.currentThread().getName() + ' :: '+ i*i); Thread.sleep(500); } } } public class Main { public static void main(String args[]) { final NumberClass number = new NumberClass(); //create thread Runnable thread = new Runnable() { public void run() { try { number.printSquares(3); } catch (InterruptedException e) { e.printStackTrace(); } } }; //start thread instance new Thread(thread, 'Thread One').start(); new Thread(thread, 'Thread Two').start(); } }
Ausgabe
Im obigen Programm haben wir eine synchronisierte Methode verwendet, um die Quadrate einer Zahl zu drucken. Die Obergrenze der Zahl wird als Argument an die Methode übergeben. Ab 1 werden dann die Quadrate jeder Zahl gedruckt, bis die Obergrenze erreicht ist.
In der Hauptfunktion wird die Thread-Instanz erstellt. Jeder Thread-Instanz wird eine Nummer zum Drucken von Quadraten übergeben.
Wie oben erwähnt, ist das Sperrobjekt an der Klasse beteiligt und nicht am Objekt, wenn eine zu synchronisierende Methode statisch ist. Dies bedeutet, dass wir die Klasse und nicht das Objekt sperren. Dies wird als statische Synchronisation bezeichnet.
Ein weiteres Beispiel ist unten angegeben.
class Table{ //synchronized static method to print squares of numbers synchronized static void printTable(int n){ for(int i=1;i<=10;i++){ System.out.print(n*i + ' '); try{ Thread.sleep(400); }catch(Exception e){} } System.out.println(); } } //thread class Thread_One class Thread_One extends Thread{ public void run(){ Table.printTable(2); } } //thread class Thread_Two class Thread_Two extends Thread{ public void run(){ Table.printTable(5); } } public class Main{ public static void main(String t[]){ //create instances of Thread_One and Thread_Two Thread_One t1=new Thread_One (); Thread_Two t2=new Thread_Two (); //start each thread instance t1.start(); t2.start(); } }
Ausgabe
Im obigen Programm drucken wir Multiplikationstabellen von Zahlen. Jede Nummer, deren Tabelle gedruckt werden soll, ist eine Thread-Instanz einer anderen Thread-Klasse. Daher drucken wir Multiplikationstabellen von 2 und 5, sodass wir zwei Klassen haben: thread_one und thread_two, um die Tabellen 2 bzw. 5 zu drucken.
Zusammenfassend führt das mit Java synchronisierte Schlüsselwort die folgenden Funktionen aus:
- Das synchronisierte Schlüsselwort in Java garantiert den sich gegenseitig ausschließenden Zugriff auf gemeinsam genutzte Ressourcen, indem ein Sperrmechanismus bereitgestellt wird. Das Sperren verhindert auch die Rennbedingungen.
- Mit dem synchronisierten Schlüsselwort verhindern wir gleichzeitige Programmierfehler im Code.
- Wenn eine Methode oder ein Block als synchronisiert deklariert wird, benötigt ein Thread eine exklusive Sperre, um die synchronisierte Methode oder den synchronisierten Block einzugeben. Nach dem Ausführen der erforderlichen Aktionen gibt der Thread die Sperre auf und löscht den Schreibvorgang. Auf diese Weise werden Speicherfehler aufgrund von Inkonsistenzen beseitigt.
Flüchtig in Java
Ein flüchtiges Schlüsselwort in Java wird verwendet, um Klassen threadsicher zu machen. Wir verwenden auch das Schlüsselwort volatile, um den Variablenwert durch verschiedene Threads zu ändern. Ein flüchtiges Schlüsselwort kann verwendet werden, um eine Variable mit primitiven Typen sowie Objekten zu deklarieren.
In bestimmten Fällen wird ein flüchtiges Schlüsselwort als Alternative für das synchronisierte Schlüsselwort verwendet. Beachten Sie jedoch, dass es kein Ersatz für das synchronisierte Schlüsselwort ist.
Wenn eine Variable als flüchtig deklariert wird, wird ihr Wert nie zwischengespeichert, sondern immer aus dem Hauptspeicher gelesen. Eine flüchtige Variable garantiert Ordnung und Sichtbarkeit. Obwohl eine Variable als flüchtig deklariert werden kann, können wir Klassen oder Methoden nicht als flüchtig deklarieren.
Betrachten Sie den folgenden Codeblock:
class ABC{ static volatile int myvar =10; }
Im obigen Code ist die Variable myvar statisch und flüchtig. Eine statische Variable wird von allen Klassenobjekten gemeinsam genutzt. Die flüchtige Variable befindet sich immer im Hauptspeicher und wird nie zwischengespeichert.
Daher befindet sich nur eine Kopie von myvar im Hauptspeicher, und alle Lese- / Schreibaktionen werden für diese Variable aus dem Hauptspeicher ausgeführt. Wenn myvar nicht als flüchtig deklariert wäre, hätte jedes Thread-Objekt eine andere Kopie, was zu Inkonsistenzen führen würde.
Einige der Unterschiede zwischen flüchtigen und synchronisierten Schlüsselwörtern sind unten aufgeführt.
Flüchtiges Schlüsselwort | Synchronisiertes Schlüsselwort |
---|---|
Das flüchtige Schlüsselwort wird nur mit Variablen verwendet. | Das synchronisierte Schlüsselwort wird mit Codeblöcken und -methoden verwendet. |
Ein flüchtiges Schlüsselwort kann den Thread nicht für das Warten blockieren. | Das synchronisierte Schlüsselwort kann den Thread für das Warten blockieren. |
Die Thread-Leistung wird mit Volatile verbessert. | Die Thread-Leistung verschlechtert sich mit der Synchronisierung etwas. |
Flüchtige Variablen befinden sich im Hauptspeicher. | Synchronisierte Konstrukte befinden sich nicht im Hauptspeicher. |
Volatile synchronisiert jeweils eine Variable zwischen Thread-Speicher und Hauptspeicher. | Das synchronisierte Schlüsselwort synchronisiert alle Variablen gleichzeitig. |
Deadlock in Java
Wir haben gesehen, dass wir mehrere Threads mit synchronisierten Schlüsselwörtern synchronisieren und Programme threadsicher machen können. Durch die Synchronisierung der Threads stellen wir sicher, dass die mehreren Threads gleichzeitig in einer Umgebung mit mehreren Threads ausgeführt werden.
Manchmal tritt jedoch eine Situation auf, in der Threads nicht mehr gleichzeitig funktionieren können. Stattdessen warten sie endlos. Dies tritt auf, wenn ein Thread auf eine Ressource wartet und diese Ressource vom zweiten Thread blockiert wird.
Der zweite Thread hingegen wartet auf die Ressource, die vom ersten Thread blockiert wird. Eine solche Situation führt in Java zu einem „Deadlock“.
Deadlock in Java wird anhand des folgenden Bildes dargestellt.
Wie aus dem obigen Diagramm ersichtlich ist, hat Thread A die Ressource r1 gesperrt und wartet auf die Ressource r2. Thread B hingegen hat die Ressource r2 blockiert und wartet auf r1.
Daher kann keiner der Threads seine Ausführung beenden, es sei denn, er erhält die ausstehenden Ressourcen. Diese Situation hat zu einem Deadlock geführt, bei dem beide Threads endlos auf die Ressourcen warten.
Im Folgenden finden Sie ein Beispiel für Deadlocks in Java.
public class Main { public static void main(String[] args) { //define shared resources final String shared_res1 = 'Java tutorials'; final String shared_res2 = 'Multithreading'; // thread_one => locks shared_res1 then shared_res2 Thread thread_one = new Thread() { public void run() { synchronized (shared_res1) { System.out.println('Thread one: locked shared resource 1'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res2) { System.out.println('Thread one: locked shared resource 2'); } } } }; // thread_two=> locks shared_res2 then shared_res1 Thread thread_two = new Thread() { public void run() { synchronized (shared_res2) { System.out.println('Thread two: locked shared resource 2'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res1) { System.out.println('Thread two: locked shared resource 1'); } } } }; //start both the threads thread_one.start(); thread_two.start(); } }
Ausgabe
Im obigen Programm haben wir zwei gemeinsam genutzte Ressourcen und zwei Threads. Beide Threads versuchen, nacheinander auf die gemeinsam genutzten Ressourcen zuzugreifen. Die Ausgabe zeigt beide Threads, die jeweils eine Ressource sperren, während sie auf die anderen warten. Dadurch entsteht eine Deadlock-Situation.
Obwohl wir Deadlock-Situationen nicht vollständig verhindern können, können wir sie sicherlich vermeiden, indem wir einige Schritte unternehmen.
Nachfolgend sind die Mittel aufgeführt, mit denen wir Deadlocks in Java vermeiden können.
# 1) Durch Vermeiden verschachtelter Sperren
Verschachtelte Sperren sind der wichtigste Grund für Deadlocks. Verschachtelte Sperren sind die Sperren, die mehreren Threads zugewiesen werden. Daher sollten wir vermeiden, mehr als einem Thread Sperren zuzuweisen.
# 2) Verwenden Sie Thread Join
Wir sollten Thread.join mit maximaler Zeit verwenden, damit die Threads die maximale Zeit für die Ausführung verwenden können. Dies verhindert einen Deadlock, der meistens auftritt, wenn ein Thread kontinuierlich auf andere wartet.
# 3) Vermeiden Sie unnötige Sperren
Wir sollten nur den notwendigen Code sperren. Unnötige Sperren für den Code können zu Deadlocks im Programm führen. Da Deadlocks den Code brechen und den Programmfluss behindern können, sollten wir geneigt sein, Deadlocks in unseren Programmen zu vermeiden.
Häufig gestellte Fragen
F # 1) Was ist Synchronisation und warum ist es wichtig?
Antworten: Bei der Synchronisierung wird der Zugriff einer gemeinsam genutzten Ressource auf mehrere Threads gesteuert. Ohne Synchronisierung können mehrere Threads die gemeinsam genutzte Ressource gleichzeitig aktualisieren oder ändern, was zu Inkonsistenzen führt.
Daher sollten wir sicherstellen, dass in einer Umgebung mit mehreren Threads die Threads synchronisiert werden, sodass sich die Art und Weise, wie sie auf die gemeinsam genutzten Ressourcen zugreifen, gegenseitig ausschließt und konsistent ist.
F # 2) Was ist Synchronisation und Nicht-Synchronisation in Java?
Antworten: Synchronisation bedeutet, dass ein Konstrukt threadsicher ist. Dies bedeutet, dass mehrere Threads nicht gleichzeitig auf das Konstrukt (Codeblock, Methode usw.) zugreifen können.
Nicht synchronisierte Konstrukte sind nicht threadsicher. Mehrere Threads können jederzeit auf die nicht synchronisierten Methoden oder Blöcke zugreifen. Eine beliebte nicht synchronisierte Klasse in Java ist StringBuilder.
F # 3) Warum ist eine Synchronisation erforderlich?
Antworten: Wenn Prozesse gleichzeitig ausgeführt werden müssen, müssen wir synchronisieren. Dies liegt daran, dass wir Ressourcen benötigen, die von vielen Prozessen gemeinsam genutzt werden können.
Um Konflikte zwischen Prozessen oder Threads beim Zugriff auf gemeinsam genutzte Ressourcen zu vermeiden, müssen diese Ressourcen synchronisiert werden, damit alle Threads Zugriff auf Ressourcen erhalten und die Anwendung auch reibungslos ausgeführt wird.
F # 4) Wie erhalten Sie eine synchronisierte ArrayList?
Antworten: Wir können die Methode Collections.synchronized list mit ArrayList als Argument verwenden, um ArrayList in eine synchronisierte Liste zu konvertieren.
F # 5) Ist HashMap synchronisiert?
wie man .bin Dateien mounten
Antworten: Nein, HashMap ist nicht synchronisiert, aber HashTable ist synchronisiert.
Fazit
In diesem Tutorial haben wir die Synchronisation von Threads ausführlich besprochen. Gleichzeitig haben wir auch das flüchtige Schlüsselwort und die Deadlocks in Java kennengelernt. Die Synchronisation besteht aus Prozess- und Thread-Synchronisation.
In einer Multithreading-Umgebung geht es uns mehr um die Thread-Synchronisation. Wir haben hier den synchronisierten Schlüsselwortansatz der Thread-Synchronisation gesehen.
Deadlock ist eine Situation, in der mehrere Threads endlos auf Ressourcen warten. Wir haben das Beispiel von Deadlocks in Java zusammen mit den Methoden zur Vermeidung von Deadlocks in Java gesehen.
=> Besuchen Sie hier, um Java von Grund auf neu zu lernen.
Literatur-Empfehlungen
- Thread.Sleep () - Thread Sleep () -Methode in Java mit Beispielen
- Java-Threads mit Methoden und Lebenszyklus
- Java-Grundlagen: Java-Syntax, Java-Klasse und Java-Kernkonzepte
- Multithreading in Java - Tutorial mit Beispielen
- Multithreading in C ++ mit Beispielen
- JAVA-Tutorial für Anfänger: Über 100 praktische Java-Video-Tutorials
- Java-Komponenten: Java Platform, JDK, JRE und Java Virtual Machine
- Java String Tutorial | Java-String-Methoden mit Beispielen