La programación avanzada con hilos en Java implica no solo la creación y ejecución básica de hilos, sino también el manejo eficiente de recursos compartidos, la coordinación entre múltiples hilos y el uso de herramientas avanzadas como ExecutorService
y Callable
. En este artículo exploraremos técnicas avanzadas para trabajar con hilos en Java.
1. Conceptos Avanzados sobre Hilos
1.1 Condiciones de Carrera
Ocurren cuando dos o más hilos acceden simultáneamente a un recurso compartido sin sincronización adecuada, lo que puede llevar a resultados impredecibles.
Ejemplo de Condición de Carrera
class Contador {
private int contador = 0;
public void incrementar() {
contador++;
System.out.println(Thread.currentThread().getName() + " - Valor: " + contador);
}
}
public class Main {
public static void main(String[] args) {
Contador contador = new Contador();
Runnable tarea = contador::incrementar;
Thread hilo1 = new Thread(tarea);
Thread hilo2 = new Thread(tarea);
hilo1.start();
hilo2.start();
}
}
Problema: Los valores de contador
pueden ser incorrectos debido al acceso concurrente.
1.2 Sincronización con synchronized
Para evitar condiciones de carrera, sincronizamos el acceso a los recursos compartidos.
class Contador {
private int contador = 0;
public synchronized void incrementar() {
contador++;
System.out.println(Thread.currentThread().getName() + " - Valor sincronizado: " + contador);
}
}
2. Comunicación entre Hilos
2.1 Métodos wait()
y notify()
Estos métodos permiten que los hilos se comuniquen entre sí al compartir un recurso común.
Ejemplo: Productor-Consumidor
class Almacen {
private int producto = -1;
private boolean disponible = false;
public synchronized void producir(int valor) throws InterruptedException {
while (disponible) {
wait(); // Espera hasta que el consumidor retire el producto
}
producto = valor;
disponible = true;
System.out.println("Producido: " + producto);
notify(); // Notifica al consumidor
}
public synchronized int consumir() throws InterruptedException {
while (!disponible) {
wait(); // Espera hasta que haya un producto disponible
}
disponible = false;
System.out.println("Consumido: " + producto);
notify(); // Notifica al productor
return producto;
}
}
public class Main {
public static void main(String[] args) {
Almacen almacen = new Almacen();
Thread productor = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
almacen.producir(i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread consumidor = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
almacen.consumir();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
productor.start();
consumidor.start();
}
}
3. Herramientas Avanzadas para Manejo de Hilos
3.1 Uso de ExecutorService
La clase ExecutorService
permite gestionar un grupo de hilos de manera eficiente, reutilizando hilos y simplificando la ejecución concurrente de tareas.
Ejemplo: Pool de Hilos
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService ejecutor = Executors.newFixedThreadPool(3); // Pool de 3 hilos
for (int i = 1; i <= 10; i++) {
final int tarea = i;
ejecutor.execute(() -> {
System.out.println("Tarea " + tarea + " ejecutada por " + Thread.currentThread().getName());
});
}
ejecutor.shutdown(); // Finaliza el pool
}
}
3.2 Uso de Callable
y Future
Callable
es similar a Runnable
, pero permite devolver un valor o lanzar excepciones. Future
se usa para manejar el resultado de una tarea.
Ejemplo con Callable
y Future
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws Exception {
ExecutorService ejecutor = Executors.newSingleThreadExecutor();
Callable<Integer> tarea = () -> {
System.out.println("Calculando...");
Thread.sleep(2000);
return 42; // Devuelve un resultado
};
Future<Integer> resultado = ejecutor.submit(tarea);
System.out.println("El resultado es: " + resultado.get()); // Espera el resultado
ejecutor.shutdown();
}
}
3.3 Bloqueos Avanzados con ReentrantLock
ReentrantLock
es una alternativa a synchronized
que proporciona más flexibilidad, como intentar obtener un bloqueo o establecer tiempos de espera.
Ejemplo con ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Contador {
private int contador = 0;
private final Lock bloqueo = new ReentrantLock();
public void incrementar() {
bloqueo.lock();
try {
contador++;
System.out.println(Thread.currentThread().getName() + " - Contador: " + contador);
} finally {
bloqueo.unlock();
}
}
}
public class Main {
public static void main(String[] args) {
Contador contador = new Contador();
Runnable tarea = contador::incrementar;
Thread hilo1 = new Thread(tarea);
Thread hilo2 = new Thread(tarea);
hilo1.start();
hilo2.start();
}
}
4. Problemas Comunes en la Programación Multihilo
- Condiciones de Carrera: Acceso no sincronizado a recursos compartidos.
- Bloqueos (Deadlocks): Ocurren cuando dos o más hilos se bloquean mutuamente esperando recursos.
- Sobrecarga de Hilos: Crear demasiados hilos puede consumir demasiados recursos.
- Interrupciones: Manejar correctamente interrupciones con
Thread.interrupt()
.
5. Prácticas Recomendadas
- Usa
ExecutorService
para manejar grupos de hilos en lugar de crearlos manualmente. - Sincroniza recursos compartidos con
synchronized
oReentrantLock
. - Divide las tareas en bloques pequeños para que los hilos no bloqueen demasiado tiempo.
- Siempre usa
try-finally
al trabajar con bloqueos para garantizar que se liberen correctamente.
Resumen
La programación avanzada con hilos en Java te permite manejar tareas concurrentes de forma eficiente y segura. Desde el uso de ExecutorService
y Callable
hasta herramientas como ReentrantLock
, estas técnicas garantizan que tus aplicaciones sean más robustas y escalables. La clave para dominar estos conceptos es practicar con casos reales y entender cómo manejar los recursos compartidos de manera efectiva.