Primero, creamos una interfaz genérica similar a Callable, que permita devolver un resultado y lanzar excepciones si es necesario. ```
package com.demo.concurrency;
public interface ProveedorResultado<V> { V procesar(); }
### 2. Construcción de la envoltura asíncrona (equivalente a FutureTask)
Para que nuestra tarea pueda ser ejecutada por un hilo tradicional (`Thread`), la clase debe implementar `Runnable`. Internamente, utilizaremos un mecanismo de bloqueo basado en monitores (`wait`/`notify`) para guardar el resultado y esperar a que esté disponible. ```
package com.demo.concurrency;
public class EnvoltorioFuturo<V> implements Runnable {
private final Object cerrojo = new Object();
private V valorRetorno;
private final ProveedorResultado<V> tarea;
public EnvoltorioFuturo(ProveedorResultado<V> tarea) {
this.tarea = tarea;
}
@Override
public void run() {
valorRetorno = tarea.procesar();
// Notificar a los hilos bloqueados en obtener() que el resultado ya está listo
synchronized (cerrojo) {
cerrojo.notifyAll();
}
}
public V obtener() throws InterruptedException {
synchronized (cerrojo) {
// Si el resultado aún no se ha computado, el hilo actual debe esperar
while (valorRetorno == null) {
cerrojo.wait();
}
return valorRetorno;
}
}
}
3. Simulación del flujo de ejecución
Probamos nuestra implementación inyectando una tarea que tarda un tiempo en completarse. El hilo principal se bloqueará al solicitar el resultado hasta que el hilo de trabajo finalice. ``` package com.demo.concurrency;
import java.time.Instant; import java.util.concurrent.TimeUnit;
public class SimulacionAsincrona { public static void main(String[] args) throws InterruptedException { ProveedorResultado<String> generador = new ProveedorResultado<String>() { @Override public String procesar() { try { TimeUnit.SECONDS.sleep(3); // Simula una operación de 3 segundos } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } return "Tarea finalizada a las: " + Instant.now(); } };
EnvoltorioFuturo<String> futuro = new EnvoltorioFuturo<>(generador);
Thread hiloTrabajador = new Thread(futuro, "Hilo-Worker");
hiloTrabajador.start();
System.out.println("Esperando resultado...");
String resultadoFinal = futuro.obtener();
System.out.println(resultadoFinal);
}
}
### 4. Alternativa avanzada: LockSupport (park/unpark)
El bloqueo con monitores (`wait`/`notify`) se puede remeplazar por `LockSupport`, una herramienta fundamental del paquete `java.util.concurrent` y del marco AQS (AbstractQueuedSynchronizer). AQS se apoya en dos componentes clave de bajo nivel: - **Unsafe**: Provee operaciones atómicas basadas en CAS (Compare-And-Swap).
- **LockSupport**: Provee primitivas de bloqueo y desbloqueo de hilos (`park` y `unpark`).
#### Conceptos principales de LockSupport
A diferencia de los monitores tradicionales, `LockSupport` opera basándose en un sistema de **permisos (permits)**: 1. **Operación sobre hilos específicos**: El método `LockSupport.unpark(Thread hilo)` actúa directamente sobre una referencia de hilo, a diferencia de `notify()`, que despierta un hilo aleatorio del pool de espera.
2. **Mecanismo de permisos**: `park()` consume un permiso si está disponible y continúa su ejecución; si no hay permiso, bloquea al hilo. `unpark(hilo)` otorga un permiso al hilo especificado (si no lo tenía), lo que lo desbloqueará si estaba esperando.
3. **Desbloqueo preventivo**: Una característica crucial es que `unpark()` puede invocarse antes que `park()`. Si un hilo productor otorga el permiso antes de que el consumidor lo solicite, cuando el consumidor llame a `park()`, encontrará el permiso disponible y continuará sin bloquearse.
4. **No acumulativo**: El permiso es binario. Llamar a `unpark()` múltiples veces solo acumula un permiso. Un único `park()` lo consumirá por comlpeto.
Ejemplo básico utilizando `LockSupport`: ```
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class DemoPermisos {
public static void main(String[] args) {
Thread hiloConsumidor = new Thread(() -> {
System.out.println("Hilo detenido, esperando permiso...");
LockSupport.park();
System.out.println("Ejecución reanudada tras recibir el permiso.");
}, "Consumidor");
Thread hiloProductor = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Otorgando permiso al hilo pausado...");
LockSupport.unpark(hiloConsumidor);
}, "Productor");
hiloConsumidor.start();
hiloProductor.start();
}
}
Interrupción de park()
El método LockSupport.park() también responde a las interrupciones de Java. Si un hilo bloqueado con park() recibe una llamada a interrupt(), despertará inmediatamente y limpiará su estado de interrupción. ```
import java.util.concurrent.locks.LockSupport;
public class DemoInterrupcion { public static void main(String[] args) { Thread hiloBloqueado = new Thread(() -> { LockSupport.park(); System.out.println("¿El hilo fue interrumpido? " + Thread.interrupted()); }, "Hilo-Interruptado");
hiloBloqueado.start();
hiloBloqueado.interrupt();
}
}
</div>