Heim  >  Artikel  >  Java  >  Einführung in Synchronized in Java Concurrent Programming Studiennotizen

Einführung in Synchronized in Java Concurrent Programming Studiennotizen

高洛峰
高洛峰Original
2017-01-05 16:20:511119Durchsuche

1. Grundlegende Verwendung von Synchronized

Synchronized ist eine der am häufigsten verwendeten Methoden zur Lösung von Parallelitätsproblemen in Java und gleichzeitig die einfachste Methode. Synchronized hat drei Hauptfunktionen: (1) Sicherstellen, dass Threads sich gegenseitig ausschließen, wenn sie auf Synchronisationscode zugreifen. (2) Sicherstellen, dass Änderungen an gemeinsam genutzten Variablen rechtzeitig sichtbar sind. (3) Das Neuordnungsproblem effektiv lösen. Grammatisch gesehen hat Synchronized insgesamt drei Verwendungszwecke:

(1) Gewöhnliche Methoden ändern

(2) Statische Methoden ändern

(3) Codeblöcke ändern

Als nächstes werde ich diese drei Verwendungsmethoden anhand mehrerer Beispielprogramme veranschaulichen (zur Vereinfachung des Vergleichs sind die drei Codeteile im Wesentlichen gleich, mit Ausnahme der unterschiedlichen Verwendungsmethoden von Synchronized).

1. Ohne Synchronisierung:

Codesegment eins:

package com.paddx.test.concurrent;
 
public class SynchronizedTest {
  public void method1(){
    System.out.println("Method 1 start");
    try {
      System.out.println("Method 1 execute");
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 1 end");
  }
 
  public void method2(){
    System.out.println("Method 2 start");
    try {
      System.out.println("Method 2 execute");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 2 end");
  }
 
  public static void main(String[] args) {
    final SynchronizedTest test = new SynchronizedTest();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method1();
      }
    }).start();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method2();
      }
    }).start();
  }
}

Die Ausführungsergebnisse sind wie folgt: Thread 1 und Thread 2 treten gleichzeitig in den Ausführungsstatus ein und die Ausführungsgeschwindigkeit von Thread 2 ist schneller als Thread 1, sodass Thread 2 zuerst ausgeführt wird. Während dieses Prozesses werden Thread 1 und Thread 2 gleichzeitig ausgeführt.

Methode 1 Start
Methode 1 ausführen
Methode 2 Start
Methode 2 ausführen
Methode 2 Ende
Methode 1 Ende

2. Gängige Methoden synchronisieren:

Code-Segment zwei:

package com.paddx.test.concurrent;
 
public class SynchronizedTest {
  public synchronized void method1(){
    System.out.println("Method 1 start");
    try {
      System.out.println("Method 1 execute");
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 1 end");
  }
 
  public synchronized void method2(){
    System.out.println("Method 2 start");
    try {
      System.out.println("Method 2 execute");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 2 end");
  }
 
  public static void main(String[] args) {
    final SynchronizedTest test = new SynchronizedTest();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method1();
      }
    }).start();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method2();
      }
    }).start();
  }
}

Die Ausführungsergebnisse sind wie folgt: Beim Vergleich mit dem ersten Code-Segment ist deutlich zu erkennen, dass Thread 2 warten muss Ausführung von Methode 1 von Thread 1. Erst nach Abschluss kann die Methode Methode2 ausgeführt werden.

Methode 1 Start
Methode 1 ausführen
Methode 1 Ende
Methode 2 Start
Methode 2 ausführen
Methode 2 Ende

3. Synchronisierung statischer Methoden (Klassen)

Codesegment drei:

package com.paddx.test.concurrent;
  
 public class SynchronizedTest {
   public static synchronized void method1(){
     System.out.println("Method 1 start");
     try {
       System.out.println("Method 1 execute");
       Thread.sleep(3000);
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
     System.out.println("Method 1 end");
   }
  
   public static synchronized void method2(){
     System.out.println("Method 2 start");
     try {
       System.out.println("Method 2 execute");
       Thread.sleep(1000);
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
     System.out.println("Method 2 end");
   }
  
   public static void main(String[] args) {
     final SynchronizedTest test = new SynchronizedTest();
     final SynchronizedTest test2 = new SynchronizedTest();
  
     new Thread(new Runnable() {
       @Override
       public void run() {
         test.method1();
       }
     }).start();
  
     new Thread(new Runnable() {
       @Override
       public void run() {
         test2.method2();
       }
     }).start();
   }
 }

Die Synchronisierung statischer Methoden ist im Wesentlichen eine Synchronisierung von Klassen (statische Methoden gehören im Wesentlichen zur Klassenmethode). (keine Methode für das Objekt). Selbst wenn Test und Test2 zu unterschiedlichen Objekten gehören, gehören sie beide zu Instanzen der SynchronizedTest-Klasse, sodass Methode1 und Methode2 nur nacheinander und nicht gleichzeitig ausgeführt werden können.

Methode 1 Start
Methode 1 ausführen
Methode 1 Ende
Methode 2 Start
Methode 2 ausführen
Methode 2 Ende

4. Codeblocksynchronisierung

Codesegment vier:

package com.paddx.test.concurrent;
 
public class SynchronizedTest {
  public void method1(){
    System.out.println("Method 1 start");
    try {
      synchronized (this) {
        System.out.println("Method 1 execute");
        Thread.sleep(3000);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 1 end");
  }
 
  public void method2(){
    System.out.println("Method 2 start");
    try {
      synchronized (this) {
        System.out.println("Method 2 execute");
        Thread.sleep(1000);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 2 end");
  }
 
  public static void main(String[] args) {
    final SynchronizedTest test = new SynchronizedTest();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method1();
      }
    }).start();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method2();
      }
    }).start();
  }
}

Die Ausführungsergebnisse sind wie folgt. Obwohl Thread 1 und Thread 2 die entsprechende Methode eingegeben haben, um die Ausführung zu starten, beginnt Thread 2 mit der Synchronisierung Block, Sie müssen warten, bis die Ausführung des Synchronisationsblocks in Thread 1 abgeschlossen ist.

Methode 1 Start
Methode 1 ausführen
Methode 2 Start
Methode 1 Ende
Methode 2 ausführen
Methode 2 Ende

2. Synchronisiertes Prinzip

Wenn Sie noch Fragen zu den oben genannten Ausführungsergebnissen haben, machen Sie sich keine Sorgen. Lassen Sie uns zunächst das Prinzip der Synchronisierung verstehen, dann werden die oben genannten Probleme auf einen Blick klar. Lassen Sie uns zunächst den folgenden Code dekompilieren, um zu sehen, wie Synchronized Codeblöcke synchronisiert:

package com.paddx.test.concurrent;
 
public class SynchronizedDemo {
  public void method() {
    synchronized (this) {
      System.out.println("Method 1 start");
    }
  }
}

Dekompilierungsergebnis:

Java 并发编程学习笔记之Synchronized简介

In Bezug auf die Funktionen dieser beiden Anweisungen haben wir Verweisen Sie direkt auf die Beschreibung in der JVM-Spezifikation:

monitorenter:

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

Die Zusammenfassung dieses Absatzes Bedeutung:

Jedes Objekt verfügt über eine Monitorsperre (Monitor). Wenn der Monitor belegt ist, befindet er sich in einem gesperrten Zustand. Wenn der Thread die Monitorenter-Anweisung ausführt, versucht er, den Besitz des Monitors zu erlangen:

1 Der Monitor ist 0, der Thread betritt den Monitor und tritt dann ein. Wenn die Zahl auf 1 gesetzt ist, ist der Thread der Besitzer des Monitors.

2. Wenn der Thread bereits den Monitor belegt und gerade wieder einsteigt, wird die Eintragsnummer im Monitor um 1 erhöht.

3 Wenn andere Threads den Monitor bereits belegt haben, Der Thread wechselt in den Blockierungsstatus, bis die Eintragsnummer des Monitors 0 ist, und versucht dann erneut, den Besitz des Monitors zu erlangen.

monitorexit:

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

Die allgemeine Bedeutung dieser Passage ist:

Der Thread, der Monitorexit ausführt, muss sein objectref Der Besitzer des entsprechenden Monitors.

Wenn die Anweisung ausgeführt wird, wird die Eintragsnummer des Monitors um 1 dekrementiert. Wenn die Eintragsnummer nach der Dekrementierung um 1 0 ist, verlässt der Thread den Monitor und ist nicht mehr der Besitzer des Monitors. Andere von diesem Monitor blockierte Threads können versuchen, den Besitz dieses Monitors zu übernehmen.

Durch diese beiden Beschreibungsabschnitte sollten wir in der Lage sein, das Implementierungsprinzip von Synchronized klar zu erkennen. Die zugrunde liegende Semantik von Synchronized wird tatsächlich durch ein Monitorobjekt vervollständigt Aus diesem Grund können Methoden wie wait/notify nur in synchronisierten Blöcken oder Methoden aufgerufen werden, andernfalls wird eine java.lang.IllegalMonitorStateException-Ausnahme ausgelöst.

Werfen wir einen Blick auf das Dekompilierungsergebnis der Synchronisationsmethode:

Quellcode:

package com.paddx.test.concurrent;
 
public class SynchronizedMethod {
  public synchronized void method() {
    System.out.println("Hello World!");
  }
}

  从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

三、运行结果解释

  有了对Synchronized原理的认识,再来看上面的程序就可以迎刃而解了。

1、代码段2结果:

  虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。

2、代码段3结果:

  虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。

3、代码段4结果:

  对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。

四 总结

  Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。


更多Java 并发编程学习笔记之Synchronized简介相关文章请关注PHP中文网!


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