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 NombreArchivoLos 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:
- Un candado (mutex) que garantiza exclusión mutua.
- Una o más variables de condición que permiten a los hilos dormir y despertar según el estado del objeto.
- El dato protegido, que vive dentro del monitor.
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:
- Espera a que el escritor actual (si lo hay) salga (
while (writer)). - Levanta la mano (
writer = true) INMEDIATAMENTE. Al hacer esto, los nuevos lectores que vayan llegando se formarán en la cola, porque ya veránwriter = true. - 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(); }
}