Cómputo Concurrente 2026

Monitores y variables de condición — ejemplos en Java

Material extra


Sobre este archivo

Cada bloque de código es autocontenido y compila con Java 21 LTS. Para ejecutar cada ejemplo, copia el código en un archivo con el nombre indicado en el comentario de la primera línea y ejecuta:

javac NombreArchivo.java
java NombreArchivo

Los ejemplos están ordenados de menor a mayor complejidad. Cada uno incluye un main con un escenario de prueba que hace visible el comportamiento del monitor, la exclusión mutua, contención y justicia.


¿Qué es un Monitor?

Un monitor encapsula tres cosas en un solo objeto:

La diferencia con un mutex simple: un hilo que no puede avanzar cede el candado y duerme en lugar de girar en un bucle ocupado. Otro hilo lo despierta cuando el estado cambia.

La regla de oro: siempre while, nunca if

// CORRECTO
lock.lock();
try {
    while (!condicionCumplida) {
        condition.await();   // libera el lock y duerme
    }
    // sección crítica
} finally {
    lock.unlock();
}

Un hilo puede despertar por una señal equivocada o por un spurious wakeup — debe volver a verificar la condición.


LockedQueue: Productor-Consumidor con dos condiciones

Cuándo usar dos condiciones

Con una sola condición habría que usar signalAll(), que despertaría tanto a productores como a consumidores aunque solo uno de los dos pueda avanzar. Dos condiciones separadas permiten despertar solo al tipo correcto con signal().

Condición Duerme a… La despierta…
notFull productores un consumidor al retirar un item
notEmpty consumidores un productor al insertar un item

Código completo

// LockedQueue.java — Compilar: javac LockedQueue.java && java LockedQueue
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class LockedQueue<T> {
    final Lock lock      = new ReentrantLock();
    final Condition notFull  = lock.newCondition(); // duerme productores
    final Condition notEmpty = lock.newCondition(); // duerme consumidores
    final Object[] items;
    int tail, head, count;

    public LockedQueue(int capacity) {
        items = new Object[capacity];
    }

    public void enq(T x) throws InterruptedException {
        System.out.println("  [" + Thread.currentThread().getName() + "] intentando entrar al monitor...");
        lock.lock();
        System.out.println("  [MUTEX] " + Thread.currentThread().getName() + " ADQUIRIÓ el monitor (enq).");
        try {
            // Espera mientras la cola esté llena
            while (count == items.length) {
                System.out.println("  [" + Thread.currentThread().getName() + "] cola llena — durmiendo en notFull y liberando mutex.");
                notFull.await();
                System.out.println("  [WAKEUP] " + Thread.currentThread().getName() + " despertó. Re-evaluando while (mitigando spurious wakeups)...");
            }
            items[tail] = x;
            if (++tail == items.length) tail = 0;
            ++count;
            System.out.println("  [" + Thread.currentThread().getName() + "] enq(" + x + ")  count=" + count);
            notEmpty.signal(); // despierta un consumidor
        } finally {
            System.out.println("  [MUTEX] " + Thread.currentThread().getName() + " LIBERÓ el monitor (enq).");
            lock.unlock();
        }
    }

    @SuppressWarnings("unchecked")
    public T deq() throws InterruptedException {
        System.out.println("  [" + Thread.currentThread().getName() + "] intentando entrar al monitor...");
        lock.lock();
        System.out.println("  [MUTEX] " + Thread.currentThread().getName() + " ADQUIRIÓ el monitor (deq).");
        try {
            // Espera mientras la cola esté vacía
            while (count == 0) {
                System.out.println("  [" + Thread.currentThread().getName() + "] cola vacía — durmiendo en notEmpty y liberando mutex.");
                notEmpty.await();
                System.out.println("  [WAKEUP] " + Thread.currentThread().getName() + " despertó. Re-evaluando while (mitigando spurious wakeups)...");
            }
            T x = (T) items[head];
            if (++head == items.length) head = 0;
            --count;
            System.out.println("  [" + Thread.currentThread().getName() + "] deq() -> " + x + "  count=" + count);
            notFull.signal(); // despierta un productor
            return x;
        } finally {
            System.out.println("  [MUTEX] " + Thread.currentThread().getName() + " LIBERÓ el monitor (deq).");
            lock.unlock();
        }
    }
        public static void main(String[] args) throws InterruptedException {
        LockedQueue<Integer> q = new LockedQueue<>(3); // capacidad 3

        // Productor: intenta insertar 6 items (más que la capacidad)
        Thread productor = new Thread(() -> {
            try {
                for (int i = 1; i <= 6; i++) {
                    q.enq(i);
                    Thread.sleep(50);
                }
            } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }, "Productor");

        // Consumidor: consume lentamente
        Thread consumidor = new Thread(() -> {
            try {
                Thread.sleep(200); // empieza tarde para forzar cola llena y suspensión del productor
                for (int i = 1; i <= 6; i++) {
                    q.deq();
                    Thread.sleep(150);
                }
            } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }, "Consumidor");

        System.out.println("=== LockedQueue: Productor-Consumidor ===");
        productor.start();
        consumidor.start();
        productor.join();
        consumidor.join();
        System.out.println("=== Fin ===");
    }
}

SimpleReadWriteLock: lectores y escritores (Versión No Justa)

La idea

Múltiples lectores pueden coexistir. Un escritor requiere acceso exclusivo. Una sola condición gestiona ambos casos.

El problema de esta versión: Inanición por la "condición unida"

Para hacer que este candado sea no justo, unimos las condiciones de bloqueo del escritor en una sola línea: while (readers > 0 || writer).

Al hacer esto, el escritor no tiene forma de "apartar su lugar" o indicar que ya está esperando. Simplemente se queda dormido esperando a que la sala esté 100% vacía. Si continúan llegando nuevos lectores sin parar, readers nunca llegará a 0, y el escritor sufrirá inanición (starvation). Además, cuando alguien sale y hace signalAll(), todos (lectores y escritores) despiertan y compiten por el candado a la fuerza, generando alta contención.

Código completo (No Justo)

// SimpleReadWriteLock.java — javac SimpleReadWriteLock.java && java SimpleRWDemo
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SimpleReadWriteLock {

    int     readers  = 0;      // lectores activos
    boolean writer   = false;  // escritor activo
    Lock      lock      = new ReentrantLock();
    Condition condition  = lock.newCondition(); // condición única

    // ── ReadLock ──────────────────────────────────────────────────────────
    public Lock readLock()  { return new ReadLock();  }
    public Lock writeLock() { return new WriteLock(); }

    class ReadLock implements Lock {
        public void lock() {
            lock.lock();
            try {
                // Si hay escritor activo, duerme
                while (writer) {
                    System.out.println("  [" + Thread.currentThread().getName() + "] lector espera (escritor activo).");
                    try { condition.await(); } catch (InterruptedException e) {}
                    System.out.println("  [WAKEUP] " + Thread.currentThread().getName() + " (Lector) despertó. Re-evaluando si el escritor ya salió...");
                }
                readers++;
                System.out.println("  [" + Thread.currentThread().getName() + "] lector entra  (readers=" + readers + ")");
            } finally { lock.unlock(); }
        }

        public void unlock() {
            lock.lock();
            try {
                readers--;
                System.out.println("  [" + Thread.currentThread().getName() + "] lector sale   (readers=" + readers + ")");
                // Último lector despierta a los escritores
                if (readers == 0) {
                    System.out.println("  [" + Thread.currentThread().getName() + "] último lector saliendo. Avisando a los demás (signalAll)...");
                    condition.signalAll();
                }
            } finally { lock.unlock(); }
        }

        public void lockInterruptibly() {}
        public boolean tryLock() { return false; }
        public boolean tryLock(long t, java.util.concurrent.TimeUnit u) { return false; }
        public java.util.concurrent.locks.Condition newCondition() { return null; }
    }

    class WriteLock implements Lock {
        public void lock() {
            lock.lock();
            try {
                // AQUÍ ESTÁ LA INJUSTICIA: Condición unida.
                // Espera pasivamente si hay CUALQUIER lector O escritor activo.
                while (readers > 0 || writer) {
                    System.out.println(" [FIRST WHILE - writer] [" + Thread.currentThread().getName() + "] escritor espera (readers=" + readers + ", writer=" + writer + ")");
                    try { condition.await(); } catch (InterruptedException e) {}
                    System.out.println("  [WAKEUP FIRST WHILE] " + Thread.currentThread().getName() + " (Escritor) despertó. Re-evaluando disponibilidad de la sala...");
                }
                // Si logra salir del while, reclama la sala
                writer = true;
                System.out.println("  [" + Thread.currentThread().getName() + "] escritor entra");
            } finally { lock.unlock(); }
        }

        public void unlock() {
            lock.lock();
            try {
                writer = false;
                System.out.println("  [" + Thread.currentThread().getName() + "] escritor sale");
                // Despierta tanto lectores como escritores (alta contención)
                System.out.println("  [" + Thread.currentThread().getName() + "] escritor liberó sala. Avisando a TODOS (signalAll)...");
                condition.signalAll();
            } finally { lock.unlock(); }
        }

        public void lockInterruptibly() {}
        public boolean tryLock() { return false; }
        public boolean tryLock(long t, java.util.concurrent.TimeUnit u) { return false; }
        public java.util.concurrent.locks.Condition newCondition() { return null; }
    }
}

// ── Demo ──────────────────────────────────────────────────────────────────────
class SimpleRWDemo {
    static int recurso = 0;

    public static void main(String[] args) throws InterruptedException {
        SimpleReadWriteLock rw = new SimpleReadWriteLock();

        Runnable leerTarea = () -> {
            System.out.println("[LLEGADA] " + Thread.currentThread().getName() + " solicita leer.");
            Lock rl = rw.readLock();
            rl.lock();
            try {
                Thread.sleep(80);
                System.out.println("  [" + Thread.currentThread().getName() + "] lee: recurso=" + recurso);
            } catch (InterruptedException e) {
            } finally { rl.unlock(); }
        };

        Runnable escribirTarea = () -> {
            System.out.println("[LLEGADA] " + Thread.currentThread().getName() + " solicita escribir.");
            Lock wl = rw.writeLock();
            wl.lock();
            try {
                recurso++;
                Thread.sleep(120);
                System.out.println("  [" + Thread.currentThread().getName() + "] escribe: recurso=" + recurso);
            } catch (InterruptedException e) {
            } finally { wl.unlock(); }
        };

        System.out.println("=== SimpleReadWriteLock ===");
        System.out.println("Observa cómo al hacer signalAll(), todos compiten por el candado.");
        Thread[] hilos = {
            new Thread(leerTarea,    "Lector-1"),
            new Thread(leerTarea,    "Lector-2"),
            new Thread(escribirTarea,"Escritor-1"),
            new Thread(leerTarea,    "Lector-3"),
            new Thread(escribirTarea,"Escritor-2"),
        };
        for (Thread t : hilos) { t.start(); Thread.sleep(20); }
        for (Thread t : hilos) { t.join(); }
        System.out.println("=== Fin ===");
    }
}

FifoReadWriteLock: Separar la condición para lograr Justicia

La diferencia clave

Para arreglar la inanición de la versión anterior, necesitamos separar la condición del escritor en dos fases.

En lugar de evaluar todo junto en un solo while, el FifoReadWriteLock hace lo siguiente:

  1. Espera a que el escritor actual (si lo hay) salga (while (writer)).
  2. Levanta la mano (writer = true) INMEDIATAMENTE. Al hacer esto, los nuevos lectores que vayan llegando se formarán en la cola, porque ya verán writer = true.
  3. Ahora sí, espera a que los lectores que ya estaban adentro terminen (while (readAcquires ! readReleases)=).
Paso Simple WriteLock (No Justo) Fifo WriteLock (Justo)
1. Llega espera readers=0 y writer=false espera solo a que writer=false
2. Avanza pone writer=true pone writer=true INMEDIATAMENTE (Levanta la mano)
3. Espera resto (ya pasó todo de golpe) espera a que lectores activos salgan

class WriteLock implements Lock {
    public void lock() {
        lock.lock();
        try {
            // Espera si hay cualquier lector O escritor activo
            while (writer) {
                System.out.println(" [FIRST WHILE - writer] [" + Thread.currentThread().getName() + "] escritor espera (readers=" + readers + ", writer=" + writer + ")");
                try { condition.await(); } catch (InterruptedException e) {}
                System.out.println("  [WAKEUP FIRST WHILE] " + Thread.currentThread().getName() + " (Escritor) despertó. Re-evaluando disponibilidad de la sala...");
            }
            writer = true;
            while (readers > 0) {
                System.out.println(" [SECOND WHILE - readers] [" + Thread.currentThread().getName() + "] escritor espera (readers=" + readers + ", writer=" + writer + ")");
                try { condition.await(); } catch (InterruptedException e) {}
                System.out.println("  [WAKEUP SECOND WHILE] " + Thread.currentThread().getName() + " (Escritor) despertó. Re-evaluando disponibilidad de la sala...");
            }
            System.out.println("  [" + Thread.currentThread().getName() + "] escritor entra");
        } finally { lock.unlock(); }
    }

    public void unlock() {
        lock.lock();
        try {
            writer = false;
            System.out.println("  [" + Thread.currentThread().getName() + "] escritor sale");
            // Despierta tanto lectores como escritores (alta contención)
            System.out.println("  [" + Thread.currentThread().getName() + "] escritor liberó sala. Avisando a TODOS (signalAll)...");
            condition.signalAll();
        } finally { lock.unlock(); }
    }