Concurrencia y subprocesamiento múltiple en Java: desde lo básico hasta técnicas avanzadas
Las aplicaciones modernas requieren el máximo rendimiento. En Java, la concurrencia y el subprocesamiento múltiple son herramientas clave para el uso eficiente de procesadores multinúcleo. En este artículo, analizaremos cómo trabajar correctamente con subprocesos, sincronización y utilidades modernas de concurrencia.
¿Qué es el subprocesamiento múltiple en Java?
El subprocesamiento múltiple es la capacidad de un programa para ejecutar varias tareas simultáneamente. En Java, cada subproceso (thread) es un proceso ligero que tiene su propia pila de llamadas. Las principales ventajas son:
- Aumento del rendimiento mediante la ejecución paralela
- Interfaz de usuario más receptiva
- Uso eficiente de los recursos de la CPU
Creación de subprocesos: dos enfoques
En Java hay dos formas principales de crear un subproceso:
1. Herencia de la clase Thread
public class MyThread extends Thread { @Override public void run() { System.out.println("El subproceso se está ejecutando: " + Thread.currentThread().getName()); }
public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // Inicio del subproceso }}2. Implementación de la interfaz Runnable
public class Task implements Runnable { @Override public void run() { System.out.println("La tarea se ejecuta en el subproceso: " + Thread.currentThread().getName()); }
public static void main(String[] args) { Thread thread = new Thread(new Task()); thread.start(); }}Se recomienda usar Runnable, ya que no limita la posibilidad de heredar de otras clases.
Sincronización de subprocesos: evitando condiciones de carrera
Cuando varios subprocesos acceden a datos compartidos, surgen problemas de sincronización. Veamos un ejemplo clásico: un contador:
public class Counter { private int count = 0;
public synchronized void increment() { count++; }
public synchronized int getCount() { return count; }}La palabra clave synchronized garantiza que solo un subproceso pueda ejecutar el método a la vez. Una alternativa es usar Lock del paquete java.util.concurrent.locks.
Deadlock: cómo evitar el bloqueo mutuo
Un deadlock ocurre cuando dos subprocesos esperan la liberación de recursos del otro. Ejemplo:
public class DeadlockExample { private final Object lock1 = new Object(); private final Object lock2 = new Object();
public void method1() { synchronized (lock1) { synchronized (lock2) { // Sección crítica } } }
public void method2() { synchronized (lock2) { synchronized (lock1) { // Sección crítica } } }}Para evitar deadlocks, siempre adquiera los bloqueos en el mismo orden o use tryLock() con tiempo de espera.
ExecutorService: gestión del grupo de subprocesos
Crear subprocesos manualmente no es eficiente. Java proporciona ExecutorService para gestionar un grupo de subprocesos:
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) { executor.submit(() -> { System.out.println("Ejecutándose en: " + Thread.currentThread().getName()); }); }
executor.shutdown(); // Finalización después de ejecutar todas las tareas }}Principales