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.