O AbstractQueuedSynchronizer (AQS) é a espinha dorsal do pacote java.util.concurrent (JUC). Ele fornece uma estrutura robusta para implementar sincronizadores dependentes de estados de bloqueio, como ReentrantLock, Semaphore e CountDownLatch.
Basicamente, o AQS gerencia dois aspectos críticos:
- Template de Bloqueio: Define métodos que subclasses implemantam para ditar regras de aquisição (justa/injusta, exclusiva/compartilhada).
- Gestão de Infraestrutura: Cuida de operações complexas como enfileiramento de threads, controle de suspensão (parking) e operações atômicas via CAS no estado do lock.
Atributos Fundamentais
A classe base gerencia o estado de sincronização através de uma variável volátil de 32 bits e uma fila do tipo FIFO (First-In-First-Out).
// Ponto de entrada da fila (sentinela ou nó que detém o lock)
private transient volatile Node head;
// Final da fila de espera
private transient volatile Node tail;
// Estado do sincronizador (0: livre, >0: ocupado ou reentrante)
private volatile int state;
Estrutura Interna: Classe Node
As threads que aguardam o acesso são encapsuladas em objetos Node, que compõem tanto a fila de sincronização quanto a fila de condição.
static final class Node {
// Estados do nó (waitStatus):
// 0: Estado inicial ao entrar na fila
// 1 (CANCELLED): Thread desistiu devido a timeout ou interrupção
// -1 (SIGNAL): O sucessor deste nó precisa ser acordado
// -2 (CONDITION): O nó está em uma fila de condição (ConditionObject)
// -3 (PROPAGATE): Usado em locks compartilhados para propagar liberações
volatile int waitStatus;
volatile Node prev; // Elo anterior na fila de sincronização
volatile Node next; // Próximo elo na fila de sincronização
volatile Thread thread; // A thread estacionada neste nó
// Define se o nó busca modo SHARED (compartilhado) ou EXCLUSIVE (exclusivo)
Node nextWaiter;
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
}
Fluxo de Operação: Modo Exclusivo
No modo exclusivo, apenas uma thread pode deter o sincronizador por vez. O método principal é o acquire.
public final void acquire(int valor) {
// Tenta obter o lock via lógica da subclasse
if (!tryAcquire(valor) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), valor)) {
selfInterrupt();
}
}
Enfileiramento de Threads
Se o tryAcquire falhar, a thread deve ser colocada na fila de forma segura utilizando CAS para evitar condições de corrida.
private Node addWaiter(Node modo) {
Node novoNo = new Node(Thread.currentThread(), modo);
Node ultimo = tail;
if (ultimo != null) {
novoNo.prev = ultimo;
if (compareAndSetTail(ultimo, novoNo)) {
ultimo.next = novoNo;
return novoNo;
}
}
// Inicializa a fila se estiver vazia ou se o CAS falhar
inserirNaFila(novoNo);
return novoNo;
}
private Node inserirNaFila(final Node no) {
for (;;) {
Node t = tail;
if (t == null) {
// Cria um nó "dummy" como cabeça inicial
if (compareAndSetHead(new Node()))
tail = head;
} else {
no.prev = t;
if (compareAndSetTail(t, no)) {
t.next = no;
return t;
}
}
}
}
Suspensão e Espera Ativa
Uma vez na fila, a thread entra em um loop onde tenta adquirir o lock se for a próxima após o head, caso contrário, ela é suspensa.
final boolean acquireQueued(final Node no, int valor) {
boolean erro = true;
try {
boolean interrompido = false;
for (;;) {
final Node anterior = no.predecessor();
// Se o anterior for o head, tenta adquirir o recurso
if (anterior == head && tryAcquire(valor)) {
setHead(no); // O nó atual vira o novo head (dummy)
anterior.next = null; // Ajuda o GC
erro = false;
return interrompido;
}
// Verifica se deve bloquear e suspende a thread
if (deveBloquear(anterior, no) && estacionarVerificarInterrupcao())
interrompido = true;
}
} finally {
if (erro) cancelarAquisicao(no);
}
}
Liberação de Recursos
Ao liberar o lock, o AQS deve notificar o próximo nó válido na fila para que ele possa retomar a execução.
public final boolean release(int valor) {
if (tryRelease(valor)) {
Node h = head;
if (h != null && h.waitStatus != 0)
notificarSucessor(h);
return true;
}
return false;
}
private void notificarSucessor(Node no) {
int status = no.waitStatus;
if (status < 0)
compareAndSetWaitStatus(no, status, 0);
Node proximo = no.next;
// Se o próximo for nulo ou cancelado, busca do fim para o começo
if (proximo == null || proximo.waitStatus > 0) {
proximo = null;
for (Node t = tail; t != null && t != no; t = t.prev)
if (t.waitStatus <= 0)
proximo = t;
}
if (proximo != null)
LockSupport.unpark(proximo.thread);
}
Modo Compartilhado e Propagação
No modo compartilhado (ex: Semaphore ou ReadLock), quando uma thread adquire o recurso, ela pode desencadear a liberação de outras threads na fila que também buscam o modo compartilhado.
private void setHeadAndPropagate(Node no, int resultado) {
Node h_antigo = head;
setHead(no);
// Se o resultado for positivo ou o status indicar sinalização, propaga
if (resultado > 0 || h_antigo == null || h_antigo.waitStatus < 0) {
Node s = no.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
Variáveis de Condição (ConditionObject)
O ConditionObject permite que threads esperem por condições específicas enquanto liberam o lock exclusivo. Ele utiliza uma fila separada (unidirecional).
- await(): Adiciona a thread à fila de condição, libera totalmente o lock (suportando reentrância) e suspende a execução.
- signal(): Move o primeiro nó da fila de condição de volta para a fila de sincronização principal do AQS para que ele dispute o lock novamente.
Visualização do Fluxo da Fila
- Estado Inicial:
headetailsão nulos. - Thread A ganha lock: O estado muda, mas a fila permanece vazia.
- Thread B falha e entra na fila:
- Um nó sentinela (Head) é criado.
- O nó da Thread B é conectado ao Head.
tailaponta para B.
- Thread A libera lock: O AQS olha para o
head, identifica o sucessor (Thread B) e o acorda viaunpark. - Thread B assume: O nó de B torna-se o novo
head, limpando sua referência de thread interna.