Java Language Synchronisation


Exemple

En Java, il existe un mécanisme de verrouillage intégré au niveau de la langue: le bloc synchronized , qui peut utiliser n'importe quel objet Java en tant que verrou intrinsèque (chaque objet Java peut être associé à un moniteur).

Les verrous intrinsèques fournissent l'atomicité à des groupes d'instructions. Pour comprendre ce que cela signifie pour nous, examinons un exemple où la synchronized est utile:

private static int t = 0;
private static Object mutex = new Object();

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
    for (int i = 0; i < 100; i++) {
        executorService.execute(() -> {
            synchronized (mutex) {
                t++;
                System.out.println(MessageFormat.format("t: {0}", t));
            }
        });
    }
    executorService.shutdown();
}

Dans ce cas, s'il n'y avait pas le bloc synchronized , il y aurait eu plusieurs problèmes de simultanéité. Le premier serait avec l'opérateur post-incrémentation (ce n'est pas atomique en lui-même), et le second serait que nous observerions la valeur de t après qu'une quantité arbitraire d'autres threads ait eu la chance de le modifier. Cependant, comme nous avons acquis un verrou intrinsèque, il n'y aura pas de conditions de course ici et la sortie contiendra des nombres de 1 à 100 dans leur ordre normal.

Les verrous intrinsèques de Java sont des mutex (c'est-à-dire des verrous d'exécution mutuelle). L'exécution mutuelle signifie que si un thread a acquis le verrou, le second sera obligé d'attendre que le premier le libère avant de pouvoir acquérir le verrou pour lui-même. Remarque: Une opération pouvant placer le thread dans l'état wait (sleep) est appelée opération de blocage . Ainsi, l'acquisition d'un verrou est une opération de blocage.

Les serrures intrinsèques en Java sont réentrantes . Cela signifie que si un thread tente d’acquérir un verrou qu’il possède déjà, il ne le bloquera pas et l’acquérera avec succès. Par exemple, le code suivant ne bloquera pas lorsqu'il sera appelé:

public void bar(){
    synchronized(this){
        ...
    }
}
public void foo(){
    synchronized(this){
        bar();
    }
}

Outre synchronized blocs synchronized , il existe également synchronized méthodes synchronized .

Les blocs de code suivants sont pratiquement équivalents (même si le bytecode semble être différent):

  1. bloc synchronized sur this :

    public void foo() {
        synchronized(this) {
            doStuff();
        }
    }
    
  2. méthode synchronized :

     public synchronized void foo() {
         doStuff();
     }
    

De même pour static méthodes static , ceci:

class MyClass {
    ...
    public static void bar() {
        synchronized(MyClass.class) {
            doSomeOtherStuff();
        }
    }
}

a le même effet que celui-ci:

class MyClass {
    ...
    public static synchronized void bar() {
        doSomeOtherStuff();
    }
}