Guide du mot clé synchronisé en Java

Guide du mot clé synchronisé en Java

1. Vue d'ensemble

Cet article rapide sera une introduction à l'utilisation du blocsynchronized en Java.

En termes simples, dans un environnement multithread, une situation de concurrence critique se produit lorsque deux threads ou plus tentent de mettre à jour des données partagées mutables en même temps. Java offre un mécanisme permettant d'éviter les situations de concurrence en synchronisant l'accès des threads aux données partagées.

Un morceau de logique marqué avecsynchronized devient un bloc synchronisé,allowing only one thread to execute at any given time.

2. Pourquoi la synchronisation?

Considérons une condition de concurrence typique dans laquelle nous calculons la somme et plusieurs threads exécutent la méthodecalculate():

public class exampleSynchronizedMethods {

    private int sum = 0;

    public void calculate() {
        setSum(getSum() + 1);
    }

    // standard setters and getters
}

Et écrivons un test simple:

@Test
public void givenMultiThread_whenNonSyncMethod() {
    ExecutorService service = Executors.newFixedThreadPool(3);
    exampleSynchronizedMethods summation = new exampleSynchronizedMethods();

    IntStream.range(0, 1000)
      .forEach(count -> service.submit(summation::calculate));
    service.awaitTermination(1000, TimeUnit.MILLISECONDS);

    assertEquals(1000, summation.getSum());
}

Nous utilisons simplement unExecutorService avec un pool de 3 threads pour exécuter lescalculate() 1000 fois.

Si nous l'exécutions en série, la sortie attendue serait 1000, maisour multi-threaded execution fails almost every time avec une sortie réelle incohérente, par exemple:

java.lang.AssertionError: expected:<1000> but was:(965)
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
...

Ce résultat n'est bien sûr pas inattendu.

Un moyen simple d'éviter la condition de concurrence est de rendre l'opération thread-safe en utilisant le mot clésynchronized.

3. Le mot-cléSynchronized

Le mot clésynchronized peut être utilisé à différents niveaux:

  • Méthodes d'instance

  • Méthodes statiques

  • Blocs de code

Lorsque nous utilisons un blocsynchronized, Java utilise en interne un moniteur également appelé verrouillage du moniteur ou verrouillage intrinsèque, pour assurer la synchronisation. Ces moniteurs étant liés à un objet, tous les blocs synchronisés d'un même objet ne peuvent être exécutés que par un seul thread à la fois.

3.1. Méthodes d'instanceSynchronized

Ajoutez simplement le mot-clésynchronized dans la déclaration de méthode pour synchroniser la méthode:

public synchronized void synchronisedCalculate() {
    setSum(getSum() + 1);
}

Notez que, une fois la méthode synchronisée, le scénario de test réussit, avec une sortie réelle égale à 1000:

@Test
public void givenMultiThread_whenMethodSync() {
    ExecutorService service = Executors.newFixedThreadPool(3);
    SynchronizedMethods method = new SynchronizedMethods();

    IntStream.range(0, 1000)
      .forEach(count -> service.submit(method::synchronisedCalculate));
    service.awaitTermination(1000, TimeUnit.MILLISECONDS);

    assertEquals(1000, method.getSum());
}

Les méthodes d'instance sontsynchronized sur l'instance de la classe propriétaire de la méthode. Ce qui signifie qu'un seul thread par instance de la classe peut exécuter cette méthode.

3.2. Synchronized Static Méthodes

Les méthodes statiques sontsynchronized tout comme les méthodes d'instance:

 public static synchronized void syncStaticCalculate() {
     staticSum = staticSum + 1;
 }

Ces méthodes sontsynchronized sur l'objetClass associé à la classe et comme il n'existe qu'un seul objetClass par JVM par classe, un seul thread peut s'exécuter à l'intérieur d'unstaticsynchronized par classe, quel que soit le nombre d'instances dont elle dispose.

Testons-le:

@Test
public void givenMultiThread_whenStaticSyncMethod() {
    ExecutorService service = Executors.newCachedThreadPool();

    IntStream.range(0, 1000)
      .forEach(count ->
        service.submit(exampleSynchronizedMethods::syncStaticCalculate));
    service.awaitTermination(100, TimeUnit.MILLISECONDS);

    assertEquals(1000, exampleSynchronizedMethods.staticSum);
}

3.3. Synchronized blocs dans les méthodes

Parfois, nous ne voulons pas synchroniser toute la méthode mais seulement quelques instructions. Ceci peut être réalisé parapplying synchronisé sur un bloc:

public void performSynchrinisedTask() {
    synchronized (this) {
        setCount(getCount()+1);
    }
}

laissez-nous tester le changement:

@Test
public void givenMultiThread_whenBlockSync() {
    ExecutorService service = Executors.newFixedThreadPool(3);
    exampleSynchronizedBlocks synchronizedBlocks = new exampleSynchronizedBlocks();

    IntStream.range(0, 1000)
      .forEach(count ->
        service.submit(synchronizedBlocks::performSynchronisedTask));
    service.awaitTermination(100, TimeUnit.MILLISECONDS);

    assertEquals(1000, synchronizedBlocks.getCount());
}

Notez que nous avons passé un paramètrethis au blocsynchronized. C'est l'objet moniteur, le code à l'intérieur du bloc est synchronisé sur l'objet moniteur. En termes simples, un seul thread par objet de contrôle peut être exécuté dans ce bloc de code.

Dans le cas où la méthode eststatic,, nous passerons le nom de classe à la place de la référence d'objet. Et la classe serait un moniteur pour la synchronisation du bloc:

public static void performStaticSyncTask(){
    synchronized (SynchronisedBlocks.class) {
        setStaticCount(getStaticCount() + 1);
    }
}

Testons le bloc dans la méthodestatic:

@Test
public void givenMultiThread_whenStaticSyncBlock() {
    ExecutorService service = Executors.newCachedThreadPool();

    IntStream.range(0, 1000)
      .forEach(count ->
        service.submit(exampleSynchronizedBlocks::performStaticSyncTask));
    service.awaitTermination(100, TimeUnit.MILLISECONDS);

    assertEquals(1000, exampleSynchronizedBlocks.getStaticCount());
}

5. Conclusion

Dans cet article rapide, nous avons vu différentes manières d'utiliser le mot-clésynchronized pour réaliser la synchronisation des threads.

Nous avons également exploré l'impact d'une situation de concurrence critique sur notre application et comment la synchronisation nous permettait d'éviter cela. Pour plus d'informations sur la sécurité des threads à l'aide de verrous en Java, reportez-vous à nosjava.util.concurrent.Locksarticle.

Le code complet de ce didacticiel est disponibleover on GitHub.