Los problemas de concurrencia suelen ser silenciosos y difíciles de depurar. Evita estos errores habituales antes de que lleguen a producción.
ArrayList, HashMap) sin protección en escritura concurrente.synchronized o atomics) deja estados viejos en caché de CPU.volatile en flags de control ocasiona bucles infinitos.volatile.ReadWriteLock.join() mal usado: hilos que se esperan mutuamente sin condición de salida.tryLock, y revisión de dependencias.import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockDemo {
private static final Lock A = new ReentrantLock();
private static final Lock B = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> bloquear(A, B), "t1");
Thread t2 = new Thread(() -> bloquear(B, A), "t2"); // orden inverso
t1.start();
t2.start();
t1.join(1000);
t2.join(1000);
System.out.println("Posible deadlock? t1 vivo=" + t1.isAlive() + " t2 vivo=" + t2.isAlive());
}
private static void bloquear(Lock primero, Lock segundo) {
primero.lock();
try {
dormir();
segundo.lock();
try {
System.out.println(Thread.currentThread().getName() + " avanzo");
} finally {
segundo.unlock();
}
} finally {
primero.unlock();
}
}
private static void dormir() {
try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
Los hilos toman locks en orden distinto y quedan esperando indefinidamente. Para evitarlo define un orden fijo o usa tryLock con timeout para abandonar si no se obtiene el segundo lock.
shutdown() deja hilos vivos y fuga recursos.import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class PoolMalo {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(1); // muy chico para 3 tareas lentas
for (int i = 1; i <= 3; i++) {
int idx = i;
pool.submit(() -> {
System.out.println("Tarea " + idx + " inicia");
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
System.out.println("Tarea " + idx + " fin");
});
}
pool.shutdown();
}
}
Un pool de 1 hilo obliga a ejecutar todo en serie. Ajusta el tamaño al hardware o usa hilos virtuales para cargas I/O-bound.
get() abusivo: bloquea el hilo llamante y puede agotar el pool si se hace dentro de tareas.CompletableFuture no bloqueante o composición asíncrona; separar pools o usar hilos virtuales para esperas I/O.import java.util.concurrent.*;
public class FuturoBloqueante {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<String> lento = pool.submit(() -> {
TimeUnit.SECONDS.sleep(5);
return "listo";
});
try {
System.out.println("Esperando con timeout...");
String valor = lento.get(1, TimeUnit.SECONDS);
System.out.println("Recibido: " + valor);
} catch (TimeoutException e) {
System.out.println("Timeout: reintentar o cancelar");
lento.cancel(true);
} finally {
pool.shutdownNow();
}
}
}
Siempre aplica timeout al esperar resultados externos. Si el tiempo límite expira, decide si reintentar, cancelar o degradar el servicio para evitar hilos colgados.