Heim  >  Artikel  >  Java  >  Verwendung des Java-Synchronized-Schlüsselworts

Verwendung des Java-Synchronized-Schlüsselworts

高洛峰
高洛峰Original
2017-01-05 17:12:511353Durchsuche

0. Leitfragecode

Der folgende Code zeigt einen Zähler und zwei Threads führen gleichzeitig eine Akkumulationsoperation für i aus, wobei jeder davon 1.000.000 Mal ausgeführt wird. Das Ergebnis, das wir erwarten, ist definitiv i=2000000. Aber nachdem wir es mehrmals ausgeführt haben, werden wir feststellen, dass der Wert von i immer kleiner als 2000000 ist. Dies liegt daran, dass das Ergebnis eines Threads das andere überschreibt, wenn zwei Threads gleichzeitig in i schreiben 🎜>

public class AccountingSync implements Runnable {
  static int i = 0;
  public void increase() {
    i++;
  }
  
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();
  
    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}

Um dieses Problem grundsätzlich zu lösen, müssen wir sicherstellen, dass mehrere Threads vollständig synchronisiert sind, wenn sie auf i ausgeführt werden. Mit anderen Worten, Thread A schreibt bei der Synchronisierung Die Aufgabe besteht darin, den synchronisierten Code zu sperren, sodass jeweils nur ein Thread den Synchronisationsblock betreten kann, wodurch die Sicherheit zwischen Threads gewährleistet wird. Genau wie im obigen Code kann die i++-Operation nur gleichzeitig ausgeführt werden Ein Thread wird ausgeführt.


2. Verwendung des synchronisierten Schlüsselworts

Objektsperre angeben: Sperren Sie das angegebene Objekt und geben Sie den synchronisierten Codeblock ein, um das angegebene Objekt zu erhalten. Die Sperre

wirkt direkt darauf Die Instanzmethode: Sie entspricht dem Sperren der aktuellen Instanz. Das Eingeben des Synchronisierungscodeblocks erfordert das Erhalten der Sperre der aktuellen Instanz (dies erfordert, dass beim Erstellen des Threads dieselbe ausführbare Instanz verwendet werden muss).

Wirkt direkt auf Statische Methoden: Äquivalent zum Sperren der aktuellen Klasse. Bevor Sie den synchronisierten Codeblock eingeben, müssen Sie die Sperre der aktuellen Klasse erhalten

2.1 Das angegebene Objekt sperren

Es gilt der folgende Code Hier ist zu beachten, dass das angegebene Objekt statisch sein muss. Andernfalls wird das Objekt nicht jedes Mal miteinander geteilt, und die Bedeutung des Sperrens ist unterschiedlich wird nicht mehr existieren.


public class AccountingSync implements Runnable {
  final static Object OBJECT = new Object();
  
  static int i = 0;
  public void increase() {
    i++;
  }
  
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (OBJECT) {
        increase();
      }
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}
2.2 Direkt auf Instanzmethoden einwirken

Das synchronisierte Schlüsselwort wirkt sich auf Instanzmethoden aus, was bedeutet, dass der Thread vor dem Aufrufen der raise()-Methode die Sperre der aktuellen Instanz erhalten muss. Dies erfordert, dass wir beim Erstellen einer Thread-Instanz dieselbe ausführbare Objektinstanz verwenden , die Thread-Sperren befinden sich nicht auf derselben Instanz und es gibt keine Möglichkeit, über Sperr-/Synchronisierungsprobleme zu sprechen.


Bitte beachten Sie Präfix der Hauptmethode. Drei Zeilen, die die korrekte Verwendung von Schlüsselwörtern für Instanzmethoden erläutern.

public class AccountingSync implements Runnable {
  static int i = 0;
  public synchronized void increase() {
    i++;
  }
  
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();
  
    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}

2.3 Direktes Einwirken auf statische Methoden

Verwenden Sie das synchronisierte Schlüsselwort für statische Methoden Dies ist nicht erforderlich. Im Beispiel müssen zwei Threads auf dieselbe Runnable-Methode verweisen. Da der Methodenblock die Sperre der aktuellen Klasse und nicht der aktuellen Instanz anfordern muss, können die Threads weiterhin korrekt synchronisiert werden 🎜>


3. Falsche Verriegelung

Aus dem obigen Beispiel wissen wir, dass wir eine Gegenanwendung benötigen, um das sicherzustellen Korrektheit der Daten, wir müssen natürlich den Zähler sperren, daher können wir den folgenden Code schreiben:
public class AccountingSync implements Runnable {
  static int i = 0;
  public static synchronized void increase() {
    i++;
  }
  
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}

Wenn wir den obigen Code ausführen , wir werden feststellen, dass die i-Ausgabe sehr klein ist. Dies zeigt, dass Threads nicht sicher sind.

Um dieses Problem zu erklären, müssen wir mit Integer beginnen: In Java ist Integer ein unveränderliches Objekt. Sobald das Objekt erstellt wurde, kann es nicht mehr geändert werden. Wenn Sie eine Ganzzahl = 1 haben, ist es immer 1. Was ist, wenn Sie dieses Objekt = 2 möchten? Nach jedem i++ ist es äquivalent zum Aufrufen der valueOf-Methode von Integer. Schauen wir uns den Quellcode der valueOf-Methode von Integer an:
public class BadLockOnInteger implements Runnable {
  static Integer i = 0;
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (i) {
        i++;
      }
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    BadLockOnInteger badLockOnInteger = new BadLockOnInteger();
  
    Thread t1 = new Thread(badLockOnInteger);
    Thread t2 = new Thread(badLockOnInteger);
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}

Integer.valueOf() ist Eigentlich eine Factory-Methode, die tendenziell ein neues Integer-Objekt zurückgibt und den Wert erneut nach i kopiert.

public static Integer valueOf(int i) {
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}
Wir kennen also die Ursache des Problems, da zwischen mehreren Threads nach i++ gilt. i zeigt auf ein neues Objekt. Daher kann es sein, dass der Thread bei jeder Sperre eine andere Objektinstanz lädt. Die Lösung ist sehr einfach: Verwenden Sie einfach eine der drei oben genannten Synchronisierungsmethoden

Weitere Artikel zur Verwendung von Java-synchronisierten Schlüsselwörtern finden Sie auf der chinesischen PHP-Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn