Maison  >  Article  >  Java  >  Introduction aux notes d'étude sur la programmation simultanée synchronisée en Java

Introduction aux notes d'étude sur la programmation simultanée synchronisée en Java

高洛峰
高洛峰original
2017-01-05 16:20:511176parcourir

1. Utilisation de base de Synchronized

Synchronized est l'une des méthodes les plus couramment utilisées pour résoudre les problèmes de concurrence en Java, et c'est également la méthode la plus simple. Synchronized a trois fonctions principales : (1) Garantir que les threads s'excluent mutuellement lors de l'accès au code de synchronisation (2) Garantir que les modifications apportées aux variables partagées peuvent être vues en temps opportun (3) Résoudre efficacement le problème de réorganisation. Grammaticalement parlant, Synchronized a un total de trois utilisations :

(1) Modifier les méthodes ordinaires

(2) Modifier les méthodes statiques

(3) Modifier les blocs de code

Ensuite, j'illustrerai ces trois méthodes d'utilisation à travers plusieurs exemples de programmes (pour faciliter la comparaison, les trois morceaux de code sont fondamentalement les mêmes à l'exception des différentes méthodes d'utilisation de Synchronized).

1. Sans synchronisation :

Segment de code un :

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();
  }
}

Les résultats de l'exécution sont les suivants, le thread 1 et le thread 2 entrent dans l'état d'exécution en même temps. , et vitesse d'exécution du thread 2 Il est plus rapide que le thread 1, donc le thread 2 est exécuté en premier Pendant ce processus, le thread 1 et le thread 2 sont exécutés en même temps.

Méthode 1 démarre
Méthode 1 exécute
Méthode 2 démarre
Méthode 2 exécute
Méthode 2 fin
Méthode 1 fin

2. Synchronisez les méthodes courantes :

Segment de code deux :

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();
  }
}

Les résultats de l'exécution sont les suivants. En comparant avec le segment de code un, il est clairement visible que le thread 2 doit attendre le. exécution de la méthode 1 du thread 1. Ce n'est qu'une fois terminé que la méthode method2 peut être exécutée.

Méthode 1 démarre
Méthode 1 exécute
Méthode 1 fin
Méthode 2 démarre
Méthode 2 exécute
Méthode 2 fin

3. Synchronisation des méthodes statiques (classes)

Segment de code trois :

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();
   }
 }

Les résultats de l'exécution sont les suivants. La synchronisation des méthodes statiques est essentiellement la synchronisation des classes (les méthodes statiques appartiennent essentiellement à la méthode des classes, pas une méthode sur l'objet), donc même si test et test2 appartiennent à des objets différents, ils appartiennent tous deux à des instances de la classe SynchronizedTest, donc méthode1 et méthode2 ne peuvent être exécutées que séquentiellement et non simultanément.

Méthode 1 démarre
Méthode 1 exécute
Méthode 1 fin
Méthode 2 démarre
Méthode 2 exécute
Méthode 2 fin

4. Synchronisation des blocs de code

Segment de code quatre :

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();
  }
}

Les résultats de l'exécution sont les suivants Bien que le thread 1 et le thread 2 aient entré la méthode correspondante pour démarrer l'exécution, le thread 2 avant d'entrer dans la synchronisation. block,Besoin d'attendre la fin de l'exécution du bloc de synchronisation dans le thread 1.

Méthode 1 démarre
Méthode 1 exécute
Méthode 2 démarre
Méthode 1 fin
Méthode 2 exécute
Méthode 2 fin

2. Principe synchronisé

Si vous avez encore des questions sur les résultats d'exécution ci-dessus, ne vous inquiétez pas. Comprenons d'abord le principe de Synchronisé, puis les problèmes ci-dessus seront clairs en un coup d'œil. Décompilons d'abord le code suivant pour voir comment Synchronized synchronise les blocs de code :

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

Résultat de la décompilation :

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

Concernant les fonctions de ces deux instructions, nous se référer directement à la description dans la spécification JVM :

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.

Le résumé de ce paragraphe Signification :

Chaque objet dispose d'un verrou de moniteur (moniteur). Lorsque le moniteur est occupé, il sera dans un état verrouillé. Lorsque le thread exécute l'instruction monitorenter, il tente d'obtenir la propriété du moniteur. Le processus est le suivant :

Si le numéro d'entrée de. le moniteur est 0, le thread entre dans le moniteur puis entre. Si le nombre est défini sur 1, le thread est le propriétaire du moniteur.

2. Si le thread occupe déjà le moniteur et vient de rentrer, le numéro d'entrée dans le moniteur sera augmenté de 1.

Si d'autres threads ont déjà occupé le moniteur, le thread entrera dans l'état de blocage jusqu'à ce que le numéro d'entrée du moniteur soit 0, puis tentera à nouveau d'obtenir la propriété du moniteur.

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.

La signification générale de ce passage est :

Le thread qui exécute monitorexit doit être objectref Le propriétaire du moniteur correspondant.

Lorsque l'instruction est exécutée, le numéro d'entrée du moniteur est décrémenté de 1. Si le numéro d'entrée est 0 après décrémentation de 1, alors le thread quitte le moniteur et n'est plus propriétaire du moniteur. D'autres threads bloqués par ce moniteur peuvent tenter d'obtenir la propriété de ce moniteur.

À travers ces deux paragraphes de description, nous devrions pouvoir voir clairement le principe d'implémentation de Synchronized. La sémantique sous-jacente de Synchronized est complétée via un objet moniteur. En fait, wait/notify et d'autres méthodes s'appuient également sur. l'objet moniteur. C'est pourquoi des méthodes telles que wait/notify ne peuvent être appelées que dans des blocs ou des méthodes synchronisés, sinon une exception java.lang.IllegalMonitorStateException sera levée.

Jetons un coup d'oeil au résultat de la décompilation de la méthode de synchronisation :

Code source :

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中文网!


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn