Les 8 scénarios de verrouillage avec synchronized en Java

Comprendre le verrouillage des objets et des classes

Point clé : Le mot-clé synchronized verrouille soit une instance d'objet, soit la classe elle-même. Les méthodes statiques synchronisées verrouillent le modèle de classe (Class object).

Scénario 1 : Enstance unique, deux méthodes synchronisées

package com.example.concurrent;

import java.util.concurrent.TimeUnit;

public class LockScenario1 {
    public static void main(String[] args) throws InterruptedException {
        Device device = new Device();
        
        Thread sender = new Thread(device::sendMessage);
        sender.start();
        TimeUnit.MILLISECONDS.sleep(5);
        
        Thread caller = new Thread(device::receiveCall);
        caller.start();
    }
}

class Device {
    public synchronized void sendMessage() {
        System.out.println("Envoi de message");
    }

    public synchronized void receiveCall() {
        System.out.println("Réception d'appel");
    }
}

Résultat : Les deux méthodes verrouillent la même instance. Le thread qui acquiert le verrou d'abord s'exécute en premier.

Envoi de message
Réception d'appel

Scénario 2 : Délai dans une méthode synchronisée

package com.example.concurrent;

import java.util.concurrent.TimeUnit;

public class LockScenario2 {
    public static void main(String[] args) throws InterruptedException {
        Device device = new Device();
        
        new Thread(device::sendMessage).start();
        TimeUnit.MILLISECONDS.sleep(5);
        new Thread(device::receiveCall).start();
    }
}

class Device {
    public synchronized void sendMessage() {
        try {
            TimeUnit.MILLISECONDS.sleep(800);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Envoi de message");
    }

    public synchronized void receiveCall() {
        System.out.println("Réception d'appel");
    }
}

Résultat : Le délai dans sendMessage n'affecet pas l'ordre car le verrou est maintenu pendant tout le temps d'exécution.

Scénario 3 : Instnaces différentes, méthodes synchronisées

package com.example.concurrent;

import java.util.concurrent.TimeUnit;

public class LockScenario3 {
    public static void main(String[] args) throws InterruptedException {
        Device senderDevice = new Device();
        Device callerDevice = new Device();
        
        new Thread(senderDevice::sendMessage).start();
        TimeUnit.MILLISECONDS.sleep(5);
        new Thread(callerDevice::receiveCall).start();
    }
}

class Device {
    public synchronized void sendMessage() {
        try {
            TimeUnit.MILLISECONDS.sleep(800);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Envoi de message");
    }

    public synchronized void receiveCall() {
        System.out.println("Réception d'appel");
    }
}

Résultat : Verrous différents sur des instances séparées permettent une exécution concurrente.

Réception d'appel
Envoi de message

Scénario 4 : Mélange de méthodes synchronisées et non synchronisées

package com.example.concurrent;

import java.util.concurrent.TimeUnit;

public class LockScenario4 {
    public static void main(String[] args) throws InterruptedException {
        Device device = new Device();
        
        new Thread(device::sendMessage).start();
        TimeUnit.MILLISECONDS.sleep(5);
        new Thread(device::receiveCall).start();
    }
}

class Device {
    public synchronized void sendMessage() {
        try {
            TimeUnit.MILLISECONDS.sleep(800);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Envoi de message");
    }

    public void receiveCall() {
        System.out.println("Réception d'appel");
    }
}

Résultat : La méthode non synchronisée ne nécessite pas de verrou, donc elle peut s'exécuter immédiatement.

Scénario 5 : Méthodes statiques synchronisées

package com.example.concurrent;

import java.util.concurrent.TimeUnit;

public class LockScenario5 {
    public static void main(String[] args) throws InterruptedException {
        new Thread(Device::sendStaticMessage).start();
        TimeUnit.MILLISECONDS.sleep(5);
        new Thread(Device::receiveStaticCall).start();
    }
}

class Device {
    public static synchronized void sendStaticMessage() {
        try {
            TimeUnit.MILLISECONDS.sleep(800);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Message statique envoyé");
    }

    public static synchronized void receiveStaticCall() {
        System.out.println("Appel statique reçu");
    }
}

Résultat : Les méthodes statiques synchronisées verrouillent l'objet Class de la classe Device.

Scénario 6 : Instances multiples avec méthodes statiques synchronisées

package com.example.concurrent;

import java.util.concurrent.TimeUnit;

public class LockScenario6 {
    public static void main(String[] args) throws InterruptedException {
        // Créer deux instances mais utiliser des méthodes statiques
        new Thread(Device::sendStaticMessage).start();
        TimeUnit.MILLISECONDS.sleep(5);
        new Thread(Device::receiveStaticCall).start();
    }
}

class Device {
    public static synchronized void sendStaticMessage() {
        try {
            TimeUnit.MILLISECONDS.sleep(800);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Message statique envoyé");
    }

    public static synchronized void receiveStaticCall() {
        System.out.println("Appel statique reçu");
    }
}

Résultat : Même avec plusieurs instances, les méthodes statiques synchronisées partagent le même verrou de classe.

Scénario 7 : Méthode statique vs instance de la même classe

package com.example.concurrent;

import java.util.concurrent.TimeUnit;

public class LockScenario7 {
    public static void main(String[] args) throws InterruptedException {
        Device device = new Device();
        
        new Thread(Device::sendStaticMessage).start();
        TimeUnit.MILLISECONDS.sleep(5);
        new Thread(device::receiveCall).start();
    }
}

class Device {
    public static synchronized void sendStaticMessage() {
        try {
            TimeUnit.MILLISECONDS.sleep(800);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Message statique envoyé");
    }

    public synchronized void receiveCall() {
        System.out.println("Réception d'appel");
    }
}

Résultat : Différents types de verrous (classe vs instance) permettent une exécution concurrente.

Scénario 8 : Instances multiples avec méthodes mixtes

package com.example.concurrent;

import java.util.concurrent.TimeUnit;

public class LockScenario8 {
    public static void main(String[] args) throws InterruptedException {
        Device staticDevice = new Device();
        Device instanceDevice = new Device();
        
        new Thread(Device::sendStaticMessage).start();
        TimeUnit.MILLISECONDS.sleep(5);
        new Thread(instanceDevice::receiveCall).start();
    }
}

class Device {
    public static synchronized void sendStaticMessage() {
        try {
            TimeUnit.MILLISECONDS.sleep(800);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Message statique envoyé");
    }

    public synchronized void receiveCall() {
        System.out.println("Réception d'appel");
    }
}

Résultat : Les deux threads opèrent sur des verrous indépendants et peuvent s'exécuter en parallèle.

Étiquettes: Java synchronized Concurrency Thread-Safety locking-mechanisms

Publié le 21 juin à 18h34