Concurrencia y multihilo en Java: nivel avanzado
\n\nEl multihilo en Java es uno de los temas más potentes y a la vez complejos. A nivel inicial usamos synchronized y Thread, pero para construir sistemas eficientes y escalables se requiere una comprensión más profunda. En este artículo analizaremos mecanismos avanzados de concurrencia: pools de hilos, ForkJoinPool, programación asíncrona con CompletableFuture, ajuste fino de bloqueos y trabajo con memoria. El material está orientado a desarrolladores que ya conocen los fundamentos del multihilo.
1. Trabajo avanzado con pools de hilos: ExecutorService y configuración
\n\nEl uso de ExecutorService es el estándar en el desarrollo industrial. Sin embargo, el estándar Executors.newFixedThreadPool(10) no siempre es adecuado. Veamos la configuración detallada de ThreadPoolExecutor.
import java.util.concurrent.*;\n\npublic class CustomThreadPool {\n public static void main(String[] args) {\n int corePoolSize = 4;\n int maxPoolSize = 8;\n long keepAliveTime = 30;\n TimeUnit unit = TimeUnit.SECONDS;\n BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);\n\n ThreadPoolExecutor executor = new ThreadPoolExecutor(\n corePoolSize,\n maxPoolSize,\n keepAliveTime,\n unit,\n workQueue,\n new ThreadPoolExecutor.CallerRunsPolicy() // política de saturación\n );\n\n // Monitoreo del estado del pool\n executor.submit(() -> {\n System.out.println("Hilos activos: " + executor.getActiveCount());\n System.out.println("Tareas completadas: " + executor.getCompletedTaskCount());\n });\n\n executor.shutdown();\n }\n}\n\nParámetros clave:
\n- \n
corePoolSize— número mínimo de hilos que se mantienen incluso sin tareas. \n maxPoolSize— número máximo de hilos. \n keepAliveTime— tiempo de vida de los hilos sobrantes. \n workQueue— cola de tareas.ArrayBlockingQueuecon tamaño limitado evita el desbordamiento de memoria. \n - Política de saturación — qué hacer si la cola está llena y todos los hilos están ocupados.
CallerRunsPolicyralentiza el hilo que llama. \n
Para tareas largas de I/O es mejor aumentar corePoolSize, para tareas CPU-bound hay que basarse en Runtime.getRuntime().availableProcessors().
2. ForkJoinPool y tareas recursivas
\n\nForkJoinPool es un pool especializado para tareas que se pueden dividir en subtareas (divide y vencerás). Utiliza eficientemente el robo de trabajo (work stealing).
Ejemplo: cálculo de la suma de elementos de un array de forma recursiva.
\n\nimport java.util.concurrent.RecursiveTask;\nimport java.util.concurrent.ForkJoinPool;\n\npublic class SumTask extends RecursiveTask<Long> {\n private static final int THRESHOLD = 1000;\n private final int[] array;\n private final int start;\n private final int end;\n\n public SumTask(int[] array, int start, int end) {\n this.array = array;\n this.start = start;\n this.end = end;\n }\n\n @Override\n protected Long compute() {\n if (end - start <= THRESHOLD) {\n long sum = 0;\n for (int i = start; i < end; i++) {\n sum += array[i];\n }\n return sum;\n }\n int mid = (start + end) / 2;\n SumTask leftTask = new SumTask(array, start, mid);\n SumTask rightTask = new SumTask(array, mid, end);\n\n leftTask.fork(); // ejecución asíncrona\n long rightResult = rightTask.compute(); // calculamos