Synchronisierte Java-Variable
-
das Schlüsselwort
synchronisiert
-
Verwenden Sie die
ReentrantLock
-Methode - Verwenden Sie das binäre oder zählende Semaphor
- Verwenden Sie die atomare Variable
In diesem Artikel wird erläutert, wie Variablen in Java synchronisiert oder gesperrt werden.
Synchronisation oder Verriegelung ist bei der Multithread-Programmierung unerlässlich. Parallel laufende Threads versuchen möglicherweise, auf dieselbe Variable oder andere Ressourcen zuzugreifen, was zu unerwarteten Ergebnissen führt.
Eine Synchronisierung oder Sperrung ist eine Lösung, um solche Fehlerfälle zu vermeiden.
das Schlüsselwort synchronisiert
Synchronisation ist eine traditionelle Methode in Java. Der synchronisierte Block wird mit dem Schlüsselwort synchronized
implementiert.
Sobald ein Thread den synchronisierten
Code eingibt, befinden sich andere Java-Threads in einem Block-Zustand. Nachdem der aktuelle Java-Thread den synchronized
-Block verlassen hat, können andere Java-Threads versuchen, auf den synchronized
-Block zuzugreifen.
Diese Methode hat einen Nachteil. Der synchronized
-Block lässt niemals zu, dass ein Thread in einer Warteschlange wartet und auf die Variable zugreift, nachdem der aktuelle Java-Thread seine Arbeit beendet hat, sodass Java-Threads lange warten müssen.
Verwenden Sie das Schlüsselwort Synchronized
mit einer Methode oder einem Block
Im folgenden Beispiel synchronisieren wir das MenuObj
in der run()
-Methode der MultiThreadList
-Klasse. Wir können den Codeblock listItem()
auch mit synchronized
definieren.
Beide Funktionen liefern das gleiche Ergebnis.
Dasselbe Programm kann mit der Synchronisation von Methoden wie folgt aussehen.
public synchronized void listItem(String item) {
{ /*Code to Synchronize*/
}
}
synchronized (this) {
System.out.println("\nMenu Item - \t" + item);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Listed - " + item);
}
Hier können Benutzer den vollständigen Beispielcode mit dem Block synchronisiert
sehen.
Beispielcode:
import java.io.*;
import java.util.*;
// Menu class
class Menu {
// List an item
public void listItem(String item) {
System.out.println("\nMenu Item - " + item);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Listed - " + item);
}
}
// Multi-thread menu listing
class MultiThreadList extends Thread {
private String item;
Menu MenuObj;
/* Gets menu object and a string item*/
MultiThreadList(String m, Menu obj) {
item = m;
MenuObj = obj;
}
public void run() {
/* Only one Java thread can list an item at a time.*/
synchronized (MenuObj) {
// Menu object synchronized
MenuObj.listItem(item);
}
}
}
// Main
class MainSync {
public static void main(String args[]) {
Menu listItem = new Menu();
MultiThreadList M1 = new MultiThreadList("Rice", listItem);
MultiThreadList M2 = new MultiThreadList("Icecream", listItem);
// Start two threads
M1.start();
M2.start();
// Wait for thread completion
try {
M1.join();
M2.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Ausgang:
Menu Item - Rice
Listed - Rice
Menu Item - Icecream
Listed - Icecream
In der obigen Ausgabe können Benutzer beobachten, dass, wenn ein Thread auf das MenuObj
zugreift, die anderen Threads dies nicht können, da es das aufgelistete Element gefolgt vom Menüelement druckt. Wenn mehrere Threads versuchen, gleichzeitig auf denselben Block zuzugreifen, sollten das Menüelement und das aufgelistete Element in einer anderen Reihenfolge gedruckt werden.
Verwenden Sie die ReentrantLock
-Methode
Die Klasse ReentrantLock
in Java ist eine flexible Methode, um Variablen zu sperren. Die Klasse ReentrantLock
sorgt für Synchronisation beim Zugriff auf gemeinsame Ressourcen oder Variablen.
Die Methoden lock()
und unlock()
führen den Vorgang durch. Jeweils ein Java-Thread erhält die Sperre. Andere Threads befinden sich während dieser Zeit in einem Blockierungszustand.
Threads können viele Male in die Sperre eintreten. Daher ist der Name Re-Entrant. Wenn der Java-Thread die Sperre erhält, ist der Hold-Zähler eins, und der Zähler wird beim Wiedereintritt addiert.
Der Lock-Hold-Zähler verringert sich um eins, nachdem unlock()
ausgeführt wurde. Der ReentrantLock
bedient die Threads basierend auf der Wartezeit.
Der Java-Thread mit einer längeren Wartezeit erhält Priorität.
Um die Sperre bei einigen Ausnahmen aufzuheben, läuft das lock()
vor dem try
-Block. Das unblock()
läuft innerhalb des finally
-Blocks.
die ReentrantLock
-Funktionen
lock() |
Erhöht den Hold-Zähler um eins. Wenn die Variable frei ist, weisen Sie dem Java-Thread eine Sperre zu. |
unlock() |
Verringert den Hold-Zähler um eins. Die Sperre wird freigegeben, wenn der Haltezähler Null ist. |
tryLock() |
Wenn die Ressource frei ist, gibt diese Funktion wahr zurück. Andernfalls wird der Thread beendet. |
lockInterruptibly() |
Wenn ein Java-Thread die Sperre verwendet, können andere Java-Threads diesen Java-Thread unterbrechen. Der aktuelle Java-Thread muss also sofort zurückkehren. |
getHoldCount() |
Gibt die Anzahl der Sperren für eine Ressource zurück. |
isHeldByCurrentThread() |
Wenn der aktuelle Java-Thread die Sperre verwendet, gibt die Methode true zurück. |
Verwenden Sie die ReentrantLock
In diesem Programm erstellen wir ein Objekt für ReentrantLock
. Die lauffähige Klasse business
wird ausgeführt und übergibt die Sperre an ReentrantLock
.
Zur Beobachtung des Ergebnisses stehen zwei Pools zur Verfügung. Das Programm verwendet lock()
, um die Sperre zu erhalten, und unlock()
, um die Sperre endgültig freizugeben.
Die booleschen Werte beendet
und verfügbar
verfolgen die Verfügbarkeit der Sperre und den Abschluss der Aufgabe.
Es laufen zwei Threads. Der zweite Shop bekommt die Sperre nicht und wartet in der Warteschlange.
Geschäft 1 erhält zuerst das Außenschloss und als nächstes das Innenschloss. Shop 2 wartet zu diesem Zeitpunkt.
Der Lock Hold Count von Shop 1 ist zwei. Geschäft 1 gibt die innere Verriegelung und dann die äußere frei, und dann verringert sich der Sperrhaltezähler, und Geschäft 1 schließt.
Shop 2 erhält die Sperre automatisch durch die Warteschlange. Der Vorgang wiederholt sich für Geschäft 2.
Beispielcode:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
class business implements Runnable {
String name;
ReentrantLock re;
public business(ReentrantLock rl, String n) {
re = rl;
name = n;
}
public void run() {
boolean finished = false;
while (!finished) {
// Get Outer Lock
boolean isLockAvailable = re.tryLock();
// If the lock is available
if (isLockAvailable) {
try {
Date d = new Date();
SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
System.out.println("Shop - " + name + " got outside lock at " + ft.format(d));
Thread.sleep(1500);
// Get Inner Lock
re.lock();
try {
d = new Date();
ft = new SimpleDateFormat("hh:mm:ss");
System.out.println("Shop - " + name + " got inside lock at " + ft.format(d));
System.out.println("Lock Hold Count - " + re.getHoldCount());
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Inner lock release
System.out.println("Shop - " + name + " releasing inside lock");
re.unlock();
}
System.out.println("Lock Hold Count - " + re.getHoldCount());
System.out.println("Shop - " + name + " closed");
finished = true;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Outer lock release
System.out.println("Shop - " + name + " releasing outside lock");
re.unlock();
System.out.println("Lock Hold Count - " + re.getHoldCount());
}
} else {
System.out.println("Shop - " + name + " waiting for lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class sample {
static final int POOL_MAX = 2;
public static void main(String[] args) {
ReentrantLock rel = new ReentrantLock();
ExecutorService pool = Executors.newFixedThreadPool(POOL_MAX);
Runnable p1 = new business(rel, "Shop 1");
Runnable p2 = new business(rel, "Shop 2");
System.out.println("Running Pool 1");
pool.execute(p1);
System.out.println("Running Pool 2");
pool.execute(p2);
pool.shutdown();
}
}
Ausgang:
Running Pool 1
Running Pool 2
Shop - Shop 2 waiting for the lock
Shop - Shop 1 got the outside lock at 11:05:47
Shop - Shop 2 waiting for the lock
Shop - Shop 1 got the inside 'lock' at 11:05:48
Lock Hold Count - 2
Shop - Shop 2 waiting for the lock
Shop - Shop 2 waiting for the lock
Shop - Shop 1 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 1 closed
Shop - Shop 1 releasing the outside lock
Lock Hold Count - 0
Shop - Shop 2 got the outside lock at 11:05:51
Shop - Shop 2 got the inside 'lock' at 11:05:52
Lock Hold Count - 2
Shop - Shop 2 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 2 closed
Shop - Shop 2 releasing the outside lock
Lock Hold Count - 0
Verwenden Sie das binäre oder zählende Semaphor
Semaphore in Java entscheidet über die Anzahl der Threads, die gemeinsam auf eine Ressource zugreifen. Binary Semaphore gibt einen Pass oder eine Genehmigung für den Zugriff auf eine Ressource in der Multithread-Programmierung.
Java-Threads müssen warten, bis eine Sperrgenehmigung verfügbar ist. Die acquire()
-Methode gibt den Pass, und die release()
-Funktion gibt den Sperrpass frei.
Die Funktion acquire()
blockiert die Threads, bis die Sperrerlaubnis verfügbar wird.
Die zwei Zustände, die in einem Semaphore
verfügbar sind, sind permit available
und permit not available
.
In diesem Beispiel greift ein Semaphore
-Objekt auf die Funktion commonResource()
zu. Im Programm laufen zwei Threads.
Die Methode acquire()
erteilt den Threads eine Genehmigung, und release()
gibt die Sperrerlaubnis je nach Verfügbarkeit frei.
Thread 0 erhält den Pass und tritt in den belegten Bereich ein. Dann kommt es nach Freigabe der Genehmigung in den freien Raum.
Als nächstes erhält Thread 1 die Sperrerlaubnis, tritt in den kritischen Bereich ein und kommt nach der Erlaubnisfreigabe in den freien Raum.
Beispielcode:
import java.util.concurrent.Semaphore;
public class SemaphoreCounter {
Semaphore binary = new Semaphore(1);
public static void main(String args[]) {
final SemaphoreCounter semObj = new SemaphoreCounter();
new Thread() {
@Override
public void run() {
semObj.commonResource();
}
}.start();
new Thread() {
@Override
public void run() {
semObj.commonResource();
}
}.start();
}
private void commonResource() {
try {
binary.acquire();
// mutual sharing resource
System.out.println(Thread.currentThread().getName() + " busy space");
} catch (InterruptedException ie) {
ie.printStackTrace();
} finally {
binary.release();
System.out.println(Thread.currentThread().getName() + " free space");
}
}
}
Ausgang:
Thread-0 busy space
Thread-0 free space
Thread-1 busy space
Thread-1 free space
Verwenden Sie die atomare Variable
Atomare Variablen bieten mit ihren integrierten Funktionen eine Synchronisation in der Java-Multithread-Programmierung.
Das Programm legt ein Zeitlimit und eine Threadgröße fest. Der executorService
übergibt den Timer per Schleife an das Thread-Limit und fährt den Dienst herunter.
Das timer
-Objekt der Atomic-Variable erhöht den Timer. Der gesamte Prozess läuft synchronisiert ab.
Jeder Java-Thread erhält eine eindeutige Zählung ohne Unterbrechung.
Beispielcode:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
class Timer implements Runnable {
private static AtomicInteger timer;
private static final int timerLimit = 10;
private static final int totThreads = 5;
public static void main(String[] args) {
timer = new AtomicInteger(0);
ExecutorService executorService = Executors.newFixedThreadPool(totThreads);
for (int i = 0; i < totThreads; i++) {
executorService.submit(new Timer());
}
executorService.shutdown();
}
@Override
public void run() {
while (timer.get() < timerLimit) {
increaseTimer();
}
}
private void increaseTimer() {
System.out.println(Thread.currentThread().getName() + " : " + timer.getAndIncrement());
}
}
Ausgang:
pool-1-thread-2 : 4
pool-1-thread-2 : 5
pool-1-thread-4 : 1
pool-1-thread-4 : 7
pool-1-thread-4 : 8
pool-1-thread-4 : 9
pool-1-thread-3 : 3
pool-1-thread-5 : 2
pool-1-thread-1 : 0
pool-1-thread-2 : 6
Dieser Artikel hat uns vier Methoden beigebracht, um eine Ressource in Java in der Multithread-Programmierung zu synchronisieren. Die synchronisierte
Methode ist die traditionelle Methode.
Wir setzen auf die ReentrantLock
-Methode, um Flexibilität zu nutzen. Das ReentrantLock
hat auch einen Nachteil: Der Programmierer muss beim Schreiben von lock()
und unlock()
den Block try-catch-finally
im Auge behalten.
Obwohl Binary Semaphore dem ReentrantLock
ähnlich ist, hat das ReentrantLock
nur die grundlegende Synchronisation und einen festen Verriegelungsmechanismus. Binary Semaphore bietet High-Level-Synchronisation, benutzerdefiniertes Sperren und Deadlock-Verhinderung.
Atomare Variablen sind auch eine Methode zum Erzielen einer Synchronisation für einfache Lese-Schreib-Aktivitäten von Daten.