ProgrammationDéveloppeur Backend

Décrivez comment fonctionne un bloc synchronized en Java. Quelles sont ses caractéristiques, comment choisir un objet pour la synchronisation et comment un mauvais choix peut-il conduire à des erreurs ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

synchronized est un mot-clé qui permet d'effectuer un accès thread-safe aux sections de code critiques. Il peut être utilisé soit pour une méthode (par exemple, public synchronized void foo()), soit pour un bloc de code (synchronized(obj) { ... }). Lorsque un thread entre dans un bloc synchronized, il obtient le moniteur (lock) de l'objet. Tant que le moniteur est occupé, d'autres threads ne peuvent pas entrer dans un autre bloc synchronized utilisant le même objet comme moniteur.

Caractéristiques :

  • La synchronisation sur le même objet assure l'exclusion mutuelle (seul un thread peut exécuter le bloc à la fois).
  • On peut se synchroniser sur this, sur un objet statique (par exemple, sur la classe) ou sur un objet arbitraire.
  • Si l'on choisit comme moniteur un objet accessible à plusieurs threads ou, au contraire, un objet trop local, la sécurité des threads peut être compromise.

Exemple de choix d'objet de synchronisation

public class Counter { private int count; private final Object lock = new Object(); // objet privé public void increment() { synchronized(lock) { count++; } } }

Pourquoi est-il préférable d'utiliser un lock privé ? Parce que si l'on se synchronise sur un objet public (par exemple, sur this ou sur une chaîne publique), du code externe peut également acquérir ce moniteur, entraînant des deadlocks ou un fonctionnement incorrect.

Question piège.

Question : Que se passe-t-il si on se synchronise sur un objet de type String contenant une valeur fixe ?

Réponse : Les chaînes en Java sont internalisées (ce sont les mêmes objets pour des littéraux identiques). Si on se synchronise sur une chaîne de type synchronized("lock"), on peut accidentellement se croiser avec un autre code qui se synchronise sur le même littéral, ce qui entraîne un blocage inattendu entre des parties totalement différentes du programme.

Exemple (à ne pas faire) :

synchronized("LOCK") { ... }

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet.


Histoire

Dans un système de trading multithread, des objets publics étaient utilisés pour la synchronisation, et du code externe a pu acquérir le lock, ce qui a entraîné des deadlocks temporaires entre des threads de différents modules et des temps d'arrêt sur le marché.


Histoire

Un jeune développeur a synchronisé l'accès à une collection sur un littéral de chaîne. Une autre partie du code s'est également synchronisée sur une chaîne avec la même valeur. Ces threads se sont mis en file d'attente les uns derrière les autres, ce qui a provoqué un ralentissement drastique de la logique métier.


Histoire

Pour la synchronisation, un nouvel objet a été choisi chaque fois : synchronized(new Object()) { ... }. En conséquence, la synchronisation ne fonctionnait pas du tout, et différents threads avaient accès simultané aux données. Cela n'a été découvert qu'au cours des tests de charge.