Deadlock dans le développement mobile : ce que c'est, causes et comment éviter le blocage mutuel

Auteur : IT Sectr Publié le : 2026-03-18 Temps de lecture : 10 min

Deadlock (blocage mutuel) est un état dans lequel deux threads ou plus attendent indéfiniment la libération de ressources détenues par d'autres participants. Selon Oracle Java Tutorials (2024), Deadlock survient lors d'une attente circulaire où chaque thread détient un verrou nécessaire à un autre thread. Sans outils de détection spéciaux, Deadlock arrête complètement l'exécution de l'application sans erreurs visibles.

Points clés

  • Deadlock — blocage mutuel de threads où chacun attend une ressource détenue par un autre thread
  • Quatre conditions de Coffman (Mutual Exclusion, Hold and Wait, No Preemption, Circular Wait) sont nécessaires à l'apparition de Deadlock
  • Deadlock diffère de Starvation en ce que les threads ne sont pas bloqués mais attendent activement dans une dépendance cyclique
  • Thread Dump — l'outil principal de détection de Deadlock dans JVM et Android Runtime
  • Hiérarchie de verrous et ordre unique d'acquisition des ressources — le principal moyen de prévenir les blocages mutuels

Qu'est-ce que Deadlock ?

Deadlock est une situation en programmation multithread où deux threads ou plus se bloquent mutuellement de façon permanente. Chaque thread détient une ressource nécessaire à un autre thread et ne la libère pas en attendant d'acquérir la ressource manquante. Par conséquent, aucun des threads ne peut poursuivre son exécution.

Dans le développement mobile, Deadlock est particulièrement critique car il ne provoque ni exceptions ni plantages. L'application cesse simplement de répondre aux actions de l'utilisateur (ANR — Application Not Responding), et la seule issue est de forcer la fin du processus. Selon Google (Android Performance Patterns, 2023), environ 15 % des rapports ANR dans Google Play Console sont liés à des blocages mutuels dans les threads d'arrière-plan.

La principale différence entre Deadlock et les autres problèmes de concurrence est son irréversibilité sans intervention extérieure. Les threads ne libéreront pas les ressources d'eux-mêmes car l'ordonnanceur du système d'exploitation ne peut pas révoquer un verrou de force. Cela distingue Deadlock de Livelock, où les threads sont actifs mais n'effectuent pas de travail utile.

Conditions de Deadlock

En 1971, Edward G. Coffman a formulé quatre conditions obligatoires nécessaires à l'apparition de Deadlock. Si au moins l'une d'elles est absente, le blocage mutuel est impossible. Ces conditions sont connues sous le nom de conditions de Coffman et constituent la base de tous les algorithmes de prévention de Deadlock.

Exclusion mutuelle (Mutual Exclusion)

Une ressource ne peut être acquise que par un seul thread à la fois. Si une ressource permet la lecture simultanée par plusieurs threads (par exemple, ReadWriteLock en mode lecture), Deadlock ne se produit pas. Cette condition découle de la nature même du Mutex et des verrous.

Détention et attente (Hold and Wait)

Un thread détient une ressource déjà acquise et attend simultanément d'acquérir une autre ressource. Si un thread peut libérer la ressource actuelle avant de demander la suivante (via un verrouillage en deux phases), la condition Hold and Wait est rompue. Sous Android, cela se manifeste souvent lorsqu'un thread détient un verrou de base de données et tente d'acquérir un verrou SharedPreferences.

Absence de préemption forcée (No Preemption)

Le système d'exploitation ne peut pas retirer de force un verrou à un thread. La ressource n'est libérée que lorsque le thread lui-même la libère. Dans certains systèmes (par exemple, SQLite en mode WAL), la préemption forcée est implémentée au niveau des opérations individuelles, ce qui réduit le risque de Deadlock.

Attente circulaire (Circular Wait)

Il existe une chaîne fermée de threads, chacun attendant une ressource détenue par le suivant dans la chaîne. Par exemple, le thread A détient la ressource 1 et attend la ressource 2, le thread B détient la ressource 2 et attend la ressource 1. C'est la seule condition qu'un développeur peut éliminer architecturalement — via une hiérarchie de verrous. Si tous les threads acquièrent les ressources dans un ordre global strictement défini, un cycle est physiquement impossible.

En pratique, dans les applications Android, Deadlock survient le plus souvent en raison de l'intersection implicite de verrous de différents niveaux : verrou de base de données (Room), verrou SharedPreferences et verrou de collection en mémoire. Chacun de ces verrous est géré par différents composants, et sans protocole centralisé d'ordre d'acquisition, les développeurs créent involontairement des cycles.

Exemple de code Deadlock en Kotlin

Considérons un exemple classique de blocage mutuel — deux threads acquièrent des verrous dans un ordre différent. Si le premier thread verrouille la ressource A et tente d'acquérir B, et le second verrouille B et tente d'acquérir A, Deadlock se produit.

kotlin
class DeadlockExample {
    private val lockA = Any()
    private val lockB = Any()

    fun operationA() {
        synchronized(lockA) {
            Thread.sleep(50)  // simulation de travail
            synchronized(lockB) {
                println("operationA terminée")
            }
        }
    }

    fun operationB() {
        synchronized(lockB) {  // ordre inverse
            Thread.sleep(50)
            synchronized(lockA) {
                println("operationB terminée")
            }
        }
    }
}

fun main() {
    val ex = DeadlockExample()
    Thread { ex.operationA() }.start()
    Thread { ex.operationB() }.start()
    // L'application se bloquera pour toujours — Deadlock !
}

Dans cet exemple, operationA acquiert lockA, et operationB acquiert lockB. Ensuite, chacun tente d'acquérir le second verrou — et les deux attendent indéfiniment. Le programme se bloque sans message d'erreur. La seule façon de corriger est de garantir le même ordre d'acquisition des verrous dans toutes les méthodes.

Deadlock vs Starvation vs Livelock

Ces trois problèmes de concurrence sont souvent confondus, mais leurs mécanismes et conséquences sont fondamentalement différents. Deadlock — arrêt complet, Starvation — attente infinie d'une ressource, Livelock — inactivité active. Comprendre les différences est crucial pour choisir la bonne stratégie de résolution.

CaractéristiqueDeadlockStarvationLivelock
État des threadsBloqués (BLOCKED)Prêts (RUNNABLE)Actifs (RUNNABLE)
Travail effectuéNonNonOui, mais inutile
CauseAttente circulaireOrdonnancement injusteGestion incorrecte des conflits
DétectionThread Dump, timeoutsSuivi de progressionCompteur de tentatives

Starvation (famine) se produit lorsque l'ordonnanceur retarde constamment l'exécution d'un thread de faible priorité au profit d'autres. Contrairement à Deadlock, le thread n'est pas bloqué — il est prêt à s'exécuter mais n'obtient pas de temps CPU. Sous Android, un scénario typique est un thread d'arrière-plan de faible priorité qui ne s'exécute jamais si le thread UI et les threads Service sont constamment actifs.

Livelock (verrou actif) est une situation où les threads ne sont pas bloqués mais réagissent infiniment aux actions des uns et des autres sans effectuer de travail utile. L'analogie classique — deux personnes se rencontrent dans un couloir et essaient toutes deux de céder le passage, se déplaçant dans la même direction. Contrairement à Deadlock, les threads en Livelock consomment du CPU, vidant la batterie de l'appareil.

Comment détecter Deadlock

Thread Dump est l'outil principal pour détecter les blocages mutuels dans JVM et Android Runtime. Lors d'un dump, la JVM analyse automatiquement le graphe de dépendances entre les moniteurs et marque les cycles de Deadlock. Dans Android Studio, les dumps de threads peuvent être obtenus via Android Profiler ou la commande kill -3 PID depuis ADB Shell.

La détection automatique de Deadlock à l'exécution est implémentée via des temporisateurs Watchdog. Si un thread ne termine pas une opération dans un délai imparti, le watchdog initie un dump et envoie un rapport au système de Crash Reporting (Firebase Crashlytics, Sentry). Selon Sentry (Issue Resolution Report, 2024), la configuration d'un watchdog réduit le temps de diagnostic de Deadlock de semaines à quelques heures.

Pendant le développement, l'analyseur statique ThreadSafe de JetBrains et Checker Framework avec le module Lock Checker sont efficaces. Ces outils analysent l'ordre d'acquisition des verrous au niveau du code source et avertissent des cycles potentiels. De plus, la détection de Deadlock pilotée par les tests (Test-Driven Deadlock Detection) est recommandée — des tests de stress qui exécutent des opérations avec différents ordres de verrouillage dans des centaines de threads.

Une attention particulière mérite la Cooperative Deadlock Detection — une méthode où les threads échangent des informations sur les verrous acquis via un registre global. Si un thread détecte un cycle potentiel, il libère toutes les ressources et réessaie l'opération. Cette approche est utilisée dans les systèmes distribués (Apache ZooKeeper, Google Chubby) et est progressivement adoptée dans le développement mobile via des bibliothèques comme Jetpack Sync.

Méthodes pour prévenir le blocage mutuel

Hiérarchie de verrous (Lock Ordering)

La méthode la plus fiable consiste à établir un ordre global d'acquisition des verrous dans toute l'application. Si tous les threads acquièrent toujours d'abord le verrou avec le numéro le plus petit, puis celui avec le numéro le plus grand, l'attente circulaire (condition Circular Wait) est impossible. Dans les grands projets, l'ordre est documenté et vérifié par la revue de code.

TryLock avec délai d'attente

TryLock est une méthode de verrouillage qui ne bloque pas un thread indéfiniment mais renvoie false si le verrou n'est pas acquis dans un délai spécifié. En Java, cela est implémenté via ReentrantLock.tryLock(timeout, TimeUnit), en Kotlin Coroutines — via Mutex.withLock avec un délai d'attente. En cas d'échec, le thread libère toutes les ressources acquises et réessaie plus tard.

Algorithme du banquier (Banker's Algorithm)

Algorithme du banquier est une méthode théorique de prévention de Deadlock proposée par Edsger Dijkstra. Il modélise l'allocation des ressources comme des transactions bancaires : le système n'alloue pas une ressource si cela pourrait conduire à un état dangereux (deadlock). En pratique, l'algorithme est rarement utilisé dans le développement mobile en raison de la difficulté de connaître à l'avance les besoins maximaux des threads, mais ses principes sont utilisés dans les bases de données SQLite et les systèmes de fichiers.

Questions fréquentes

Deadlock peut-il se produire dans une application mono-thread ?

Non, le blocage mutuel nécessite au moins deux threads. Dans le code mono-thread, toutes les opérations sont exécutées séquentiellement, donc l'attente circulaire est impossible. Cependant, Deadlock peut se produire entre processus lors de l'utilisation de verrous de fichiers ou de sémaphores interprocessus.

En quoi Deadlock dans Kotlin Coroutines diffère-t-il de Deadlock dans les threads ?

Dans les coroutines, Deadlock se produit au niveau des fonctions suspend et ne bloque pas le thread OS, ce qui le rend moins perceptible. Mutex de kotlinx.coroutines est un verrou suspendu (suspending) — il ne bloque pas le thread, mais la coroutine ne s'exécute pas. Pour la détection, utilisez DebugProbes du module kotlinx-coroutines-debug.

Qu'est-ce que Deadlock dans SQLite sur Android ?

Deadlock dans SQLite se produit lorsque deux connexions à la base de données tentent d'exécuter des transactions dans des ordres différents. SQLite détecte ces situations et renvoie le code d'erreur SQLITE_BUSY ou SQLITE_LOCKED. Sous Android, il est recommandé d'utiliser Room avec une seule instance de base de données et des transactions via @Transaction, ce qui élimine le Deadlock inter-connexions.

Comment Android détecte-t-il Deadlock ?

Android Runtime dispose d'un détecteur de Deadlock intégré qui s'exécute lors de la génération d'un ANR (Application Not Responding). Le système analyse le Thread Dump de tous les threads de l'application et marque les blocages mutuels. Le résultat est disponible dans /data/anr/traces.txt et dans Google Play Console dans la section ANR Reports.

Que faire si Deadlock est trouvé en production ?

Commencez par obtenir un Thread Dump de tous les threads de l'application. Analysez quels verrous chaque thread détient et lesquels il tente d'acquérir. Implémentez un temporisateur Watchdog avec dump automatique en cas de dépassement du délai. Après correction, ajoutez la règle lint ThreadSafety à votre pipeline CI pour prévenir les récidives.

Résumé

  • Deadlock — blocage mutuel où les threads attendent indéfiniment des ressources détenues les uns par les autres
  • Quatre conditions de Coffman (Mutual Exclusion, Hold and Wait, No Preemption, Circular Wait) sont nécessaires à Deadlock
  • Thread Dump — la méthode standard pour détecter les blocages mutuels dans JVM et Android Runtime
  • Hiérarchie de verrous avec un ordre global unique élimine complètement la condition d'attente circulaire
  • TryLock avec délai d'attente empêche l'attente infinie et permet au thread de gérer correctement l'indisponibilité de la ressource
  • Deadlock vs Starvation — dans Deadlock les threads sont bloqués, dans Starvation ils sont prêts à s'exécuter mais n'obtiennent pas de CPU
  • Temporisateurs Watchdog et analyseurs statiques (ThreadSafe, Checker Framework) — protection de base contre Deadlock dans CI/CD

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi