Introduction à AQS
L'AbstractQueuedSynchronizer (AQS) est une classe fondamentale du package java.util.concurrent.locks, servant de base à plusieurs mécanismes de synchronisation dans l'écosystème Java. Elle fournit un cadre robuste pour la gestion des accès concurrents via deux variantes : AbstractQueuedSynchronizer utilisant un état de type int, et AbstractQueuedLongSynchronizer utilisant un long. Ce cadre repose sur un patron de conception dit « méthode template », permettant aux classes filles de définir leur comportement spécifique tout en bénéficiant d'une implémentation commune pour la gestion des files d'attente et de l'état de verrou.
Gestion de l'état de synchronisation (state)
La variable state représente l'état interne du synchroniseur. Elle est déclarée comme volatile afin d’assurer sa visibilité entre threads, et son accès est encadré par trois méthodes protégées :
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newValue) {
state = newValue;
}
protected final boolean compareAndSetState(int expected, int update) {
return UNSAFE.compareAndSwapInt(this, STATE_OFFSET, expected, update);
}
Cette dernière méthode s'appuie sur des opérations atomiques fournies par Unsafe (ou désormais VarHandle depuis Java 9), garantissant une modification thread-safe de state.
Le sens concret de state varie selon l'implémentation concrète :
- Dans
ReentrantLock,stateindique le nombre de fois où le verrou a été acquis par le même thread (compteur de réentrance). - Pour
CountDownLatch,statejoue le rôle d’un compteur décroissant ; tant qu’il n’est pas nul, les threads restent bloqués. - Avec
Semaphore,statereflète le nombre de permis disponibles. Un appel àacquire()décrémente cet état, etrelease()l’incrémente.
Structure de la file d'attente : le nœud Node
Lorsqu’un thread échoue à acquérir le verrou, il est placé dans une file d’attente interne gérée par AQS. Cette file est une liste doublement chaînée FIFO, composée d’instances de la classe interne Node.
Les pointeurs head et tail sont initialisés à null. La création du premier nœud (nœud sentinelle) se fait de manière paresseuse lorsqu’un thread doit être mis en attente. Ce nœud initial ne contient aucun thread actif ; il sert uniquement de point d’ancrage à la structure.
Chaque Node contient :
- Référence au thread associé
- Prédécesseur et successeur dans la liste
- Type de mode d’attente : exclusif (
EXCLUSIVE) ou partagé (SHARED) - État du nœud (
waitStatus) : peut êtreCANCELLED,SIGNAL,CONDITION, etc.
L’ajout d’un nouveau nœud à la fin de la file utilise une opération CAS (Compare-And-Swap) pour assurer la sécurité face aux accès concurrents.
Deux modes d'accès : exclusif et partagé
AQS distingue deux grands modes de synchronisation :
Mode exclusif (独占)
Un seul thread peut posséder le verrou à un instant donné. Exemples :
ReentrantLockReentrantReadWriteLock.WriteLock
Mode partagé (共享)
Plusieurs threads peuvent accéder simultanément à la rsesource. Exemples :
CountDownLatchCyclicBarrierSemaphoreReentrantReadWriteLock.ReadLock
Pour supporter ces deux modes, AQS expose des méthodes abstraites que les sous-classes doivent implémenter :
// Mode exclusif
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); }
// Mode partagé
protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }
protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }
// Vérifie si le thread courant détient le verrou exclusivement
protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); }
Une implémentation n’a besoin de fournir que les méthodes correspondant à son mode d’opération. Par exemple, ReentrantLock implémente uniquement les méthodes du mode exclusif, tandis que Semaphore se concentre sur les vesrions partagées.
Comment ces modèles sont-ils concrètement appliqués dans les algorithmes d’acquisition et de libération ? Cela fera l’objet d’une analyse approfondie dans la suite de cette série.