Synchronisierte Java-Variable

Shubham Vora 12 Oktober 2023
  1. das Schlüsselwort synchronisiert
  2. Verwenden Sie die ReentrantLock-Methode
  3. Verwenden Sie das binäre oder zählende Semaphor
  4. Verwenden Sie die atomare Variable
Synchronisierte Java-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.

Shubham Vora avatar Shubham Vora avatar

Shubham is a software developer interested in learning and writing about various technologies. He loves to help people by sharing vast knowledge about modern technologies via different platforms such as the DelftStack.com website.

LinkedIn GitHub

Verwandter Artikel - Java Variable