Heim  >  Artikel  >  Java  >  Beispieldemonstration von Java mit der synchronisierten modifizierten Methode zum Synchronisieren von Threads

Beispieldemonstration von Java mit der synchronisierten modifizierten Methode zum Synchronisieren von Threads

高洛峰
高洛峰Original
2017-01-05 15:34:081462Durchsuche

Sie können das Schlüsselwort synchronisiert in Java für die Thread-Synchronisationssteuerung verwenden, um einen sequentiellen Zugriff auf Schlüsselressourcen zu erreichen und Probleme wie Dateninkonsistenzen zu vermeiden, die durch die gleichzeitige Ausführung mehrerer Threads verursacht werden. Das Prinzip der Synchronisierung ist der Objektmonitor (Sperre). Nur der Thread, der den Monitor erhält, kann weiterhin ausgeführt werden, andernfalls wartet der Thread auf den Erhalt des Monitors. Jedem Objekt oder jeder Klasse in Java ist eine Sperre zugeordnet. Bei einem Objekt wird die Instanzvariable des Objekts überwacht (eine Klasse selbst ist ein Objekt der Klasse Klasse). Die der Klasse zugeordnete Sperre ist also auch eine Objektsperre. Es gibt zwei Möglichkeiten, das synchronisierte Schlüsselwort zu verwenden: die synchronisierte Methode und der synchronisierte Block. Beide Überwachungsbereiche sind mit einem eingeführten Objekt verknüpft. Bei der Ankunft in diesem Überwachungsbereich sperrt die JVM das Referenzobjekt und gibt die Sperre für das Referenzobjekt frei, wenn sie es verlässt (bei einem Ausnahmeausgang gibt die JVM die Sperre frei). . Die Objektsperre ist ein interner Mechanismus der JVM. Sie müssen lediglich eine Synchronisationsmethode oder einen Synchronisationsblock schreiben. Beim Betrieb des Überwachungsbereichs erwirbt oder gibt die JVM die Sperre automatisch frei.

Beispiel 1

Schauen wir uns zunächst das erste Beispiel an. In Java darf nur auf einen kritischen Abschnitt desselben Objekts gleichzeitig zugegriffen werden (alles sind nicht statische synchronisierte Methoden). ):

package concurrency;
 
public class Main8 {
  public static void main(String[] args) {
    Account account = new Account();
    account.setBalance(1000);
    Company company = new Company(account);
    Thread companyThread = new Thread(company);
    Bank bank = new Bank(account);
    Thread bankThread = new Thread(bank);
    System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
    companyThread.start();
    bankThread.start();
    try {
      //join()方法等待这两个线程运行完成
      companyThread.join();
      bankThread.join();
      System.out.printf("Account : Final Balance: %f\n", account.getBalance());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
/*帐户*/
class Account{
  private double balance;
  /*将传入的数据加到余额balance中*/
  public synchronized void addAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp += amount;
    balance = tmp;
  }
  /*将传入的数据从余额balance中扣除*/
  public synchronized void subtractAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp -= amount;
    balance = tmp;
  }
  public double getBalance() {
    return balance;
  }
  public void setBalance(double balance) {
    this.balance = balance;
  }
}
/*银行*/
class Bank implements Runnable{
  private Account account;
  public Bank(Account account){
    this.account = account;
  }
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      account.subtractAmount(1000);
    }
  }
}
/*公司*/
class Company implements Runnable{
  private Account account;
  public Company(Account account){
    this.account = account;
  }
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      account.addAmount(1000);
    }
  }
}

Sie haben eine simulierte Bankkontoanwendung entwickelt, die Guthaben aufladen und abbuchen kann. Dieses Programm lädt das Konto auf, indem es die Methode addAmount() 100 Mal aufruft und jedes Mal 1000 einzahlt. Anschließend zieht es den Kontostand ab, indem es die Methode subtractAmount() 100 Mal aufruft und jedes Mal 1000 abzieht Dasselbe wie beim anfänglichen Gleichgewicht. Gleichheit wird durch das synchronisierte Schlüsselwort erreicht.

Wenn Sie das Problem des gleichzeitigen Zugriffs auf gemeinsam genutzte Daten überprüfen möchten, müssen Sie nur das synchronisierte Schlüsselwort in den Methodendeklarationen addAmount() und subtractAmount() löschen. Ohne das synchronisierte Schlüsselwort sind die gedruckten Saldowerte inkonsistent. Wenn Sie dieses Programm mehrmals ausführen, erhalten Sie unterschiedliche Ergebnisse. Da die JVM die Ausführungsreihenfolge von Threads nicht garantiert, lesen und ändern die Threads den Kontostand bei jeder Ausführung in einer anderen Reihenfolge, was zu einem anderen Endergebnis führt.

Die Methode eines Objekts wird mit dem synchronisierten Schlüsselwort deklariert und kann nur von einem Thread aufgerufen werden. Wenn Thread A eine Synchronisationsmethode syncMethodA() ausführt und Thread B andere Synchronisationsmethoden syncMethodB() dieses Objekts ausführen möchte, wird Thread B blockiert, bis Thread A den Zugriff abschließt. Wenn Thread B jedoch auf verschiedene Objekte derselben Klasse zugreift, wird keiner der Threads blockiert.

Beispiel 2

Demonstriert das Problem, dass mehrere Threads gleichzeitig auf statische synchronisierte Methoden und nicht statische synchronisierte Methoden für dasselbe Objekt zugreifen können.

package concurrency;
 
public class Main8 {
  public static void main(String[] args) {
    Account account = new Account();
    account.setBalance(1000);
    Company company = new Company(account);
    Thread companyThread = new Thread(company);
    Bank bank = new Bank(account);
    Thread bankThread = new Thread(bank);
    System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
    companyThread.start();
    bankThread.start();
    try {
      //join()方法等待这两个线程运行完成
      companyThread.join();
      bankThread.join();
      System.out.printf("Account : Final Balance: %f\n", account.getBalance());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
/*帐户*/
class Account{
  /*这里改为静态变量*/
  private static double balance = 0;
  /*将传入的数据加到余额balance中,注意是用static修饰过的*/
  public static synchronized void addAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp += amount;
    balance = tmp;
  }
  /*将传入的数据从余额balance中扣除*/
  public synchronized void subtractAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp -= amount;
    balance = tmp;
  }
  public double getBalance() {
    return balance;
  }
  public void setBalance(double balance) {
    this.balance = balance;
  }
}
/*银行*/
class Bank implements Runnable{
  private Account account;
  public Bank(Account account){
    this.account = account;
  }
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      account.subtractAmount(1000);
    }
  }
}
/*公司*/
class Company implements Runnable{
  private Account account;
  public Company(Account account){
    this.account = account;
  }
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      account.addAmount(1000);
    }
  }
}

Ich habe gerade den Kontostand im vorherigen Beispiel geändert, indem ich das Schlüsselwort static hinzugefügt habe. Die Methode addAmount() kann auch mit dem Schlüsselwort static geändert werden. Sie können die Ausführungsergebnisse selbst testen. Jede Ausführung führt zu unterschiedlichen Ergebnissen!

Einige Zusammenfassung:

synchronisiert wird durch Software (JVM) implementiert und ist einfach und benutzerfreundlich. Auch mit Lock nach JDK5 ist es immer noch weit verbreitet.

Synchronisiert ist tatsächlich unfair, und Threads, die schon lange im Wartebereich warten, können erneut warten.

Synchronisierte Sperren sind nur mit einer Bedingung verknüpft (ob die Sperre erworben werden soll) und sind unflexibel. Später wurde dieses Problem durch die Kombination von Bedingung und Sperre gelöst.

Wenn mehrere Threads um eine Sperre konkurrieren, können die verbleibenden Threads, die die Sperre nicht erhalten haben, ohne Unterbrechung nur weiterhin versuchen, die Sperre zu erhalten. Eine hohe Parallelität führt zu Leistungseinbußen. Die lockInterruptably()-Methode von ReentrantLock kann die Reaktion auf Interrupts priorisieren. Wenn ein Thread zu lange wartet, kann er sich selbst unterbrechen. Anschließend reagiert ReentrantLock auf die Unterbrechung und lässt den Thread nicht länger warten. Mit diesem Mechanismus kommt es bei Verwendung von ReentrantLock nicht zu Deadlocks wie bei der Synchronisierung.

Weitere Beispiele und Demonstrationen von Java mit der synchronisierten Änderungsmethode zum Synchronisieren von Threads 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