En este tema, exploraremos las utilidades de concurrencia en Java, que son herramientas y clases proporcionadas por el lenguaje para facilitar la programación concurrente. Estas utilidades ayudan a gestionar múltiples hilos de manera eficiente y segura, evitando problemas comunes como las condiciones de carrera y los bloqueos.
Contenidos
- Introducción a las Utilidades de Concurrencia
- Executor Framework
- Callable y Future
- ScheduledExecutorService
- CountDownLatch
- CyclicBarrier
- Semaphore
- Exchanger
- Ejercicios Prácticos
- Introducción a las Utilidades de Concurrencia
Java proporciona un paquete completo para la concurrencia: java.util.concurrent
. Este paquete incluye una serie de clases y interfaces que facilitan la creación y gestión de hilos, así como la sincronización entre ellos.
- Executor Framework
El Executor Framework
es una API que proporciona una forma estándar de gestionar un grupo de hilos. La interfaz principal es Executor
, y sus implementaciones más comunes son ThreadPoolExecutor
y ScheduledThreadPoolExecutor
.
Ejemplo: Uso de ExecutorService
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { executor.submit(new Task(i)); } executor.shutdown(); } } class Task implements Runnable { private int taskId; public Task(int id) { this.taskId = id; } @Override public void run() { System.out.println("Task " + taskId + " is running"); } }
Explicación
- ExecutorService: Es una interfaz que representa un grupo de hilos reutilizables.
- Executors.newFixedThreadPool(3): Crea un pool de hilos con un tamaño fijo de 3.
- submit: Envía una tarea al pool de hilos para su ejecución.
- shutdown: Finaliza el pool de hilos una vez que todas las tareas han sido completadas.
- Callable y Future
Callable
es similar a Runnable
, pero puede devolver un resultado y lanzar una excepción. Future
representa el resultado de una operación asincrónica.
Ejemplo: Uso de Callable y Future
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); Future<Integer> future = executor.submit(new Task()); try { Integer result = future.get(); System.out.println("Result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } } class Task implements Callable<Integer> { @Override public Integer call() throws Exception { return 123; } }
Explicación
- Callable: Una interfaz que permite devolver un resultado y lanzar excepciones.
- Future: Representa el resultado de una operación asincrónica.
- future.get(): Bloquea hasta que el resultado esté disponible.
- ScheduledExecutorService
ScheduledExecutorService
permite programar tareas para que se ejecuten después de un retraso o de manera periódica.
Ejemplo: Uso de ScheduledExecutorService
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledExecutorExample { public static void main(String[] args) { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(new Task(), 0, 1, TimeUnit.SECONDS); } } class Task implements Runnable { @Override public void run() { System.out.println("Task is running"); } }
Explicación
- ScheduledExecutorService: Permite programar tareas con un retraso inicial y una frecuencia fija.
- scheduleAtFixedRate: Programa una tarea para que se ejecute periódicamente.
- CountDownLatch
CountDownLatch
es una clase de sincronización que permite que uno o más hilos esperen hasta que un conjunto de operaciones en otros hilos se complete.
Ejemplo: Uso de CountDownLatch
import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(new Task(latch)).start(); } latch.await(); System.out.println("All tasks are completed"); } } class Task implements Runnable { private CountDownLatch latch; public Task(CountDownLatch latch) { this.latch = latch; } @Override public void run() { System.out.println("Task is running"); latch.countDown(); } }
Explicación
- CountDownLatch: Permite que un hilo espere hasta que el contador llegue a cero.
- latch.await(): Bloquea hasta que el contador llegue a cero.
- latch.countDown(): Decrementa el contador.
- CyclicBarrier
CyclicBarrier
es una clase de sincronización que permite que un conjunto de hilos esperen entre sí en un punto común.
Ejemplo: Uso de CyclicBarrier
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierExample { public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All tasks are completed")); for (int i = 0; i < 3; i++) { new Thread(new Task(barrier)).start(); } } } class Task implements Runnable { private CyclicBarrier barrier; public Task(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { System.out.println("Task is running"); try { barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } }
Explicación
- CyclicBarrier: Permite que un conjunto de hilos esperen entre sí en un punto común.
- barrier.await(): Bloquea hasta que todos los hilos lleguen al punto de barrera.
- Semaphore
Semaphore
es una clase de sincronización que controla el acceso a un recurso compartido mediante un contador.
Ejemplo: Uso de Semaphore
import java.util.concurrent.Semaphore; public class SemaphoreExample { public static void main(String[] args) { Semaphore semaphore = new Semaphore(1); for (int i = 0; i < 3; i++) { new Thread(new Task(semaphore)).start(); } } } class Task implements Runnable { private Semaphore semaphore; public Task(Semaphore semaphore) { this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println("Task is running"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } } }
Explicación
- Semaphore: Controla el acceso a un recurso compartido mediante un contador.
- semaphore.acquire(): Adquiere un permiso del semáforo.
- semaphore.release(): Libera un permiso del semáforo.
- Exchanger
Exchanger
es una clase de sincronización que permite que dos hilos intercambien datos entre sí.
Ejemplo: Uso de Exchanger
import java.util.concurrent.Exchanger; public class ExchangerExample { public static void main(String[] args) { Exchanger<String> exchanger = new Exchanger<>(); new Thread(new Task(exchanger, "Data from Thread 1")).start(); new Thread(new Task(exchanger, "Data from Thread 2")).start(); } } class Task implements Runnable { private Exchanger<String> exchanger; private String data; public Task(Exchanger<String> exchanger, String data) { this.exchanger = exchanger; this.data = data; } @Override public void run() { try { String exchangedData = exchanger.exchange(data); System.out.println("Exchanged data: " + exchangedData); } catch (InterruptedException e) { e.printStackTrace(); } } }
Explicación
- Exchanger: Permite que dos hilos intercambien datos entre sí.
- exchanger.exchange(data): Intercambia datos con el otro hilo.
- Ejercicios Prácticos
Ejercicio 1: Uso de ExecutorService
Descripción: Crea un programa que utilice ExecutorService
para ejecutar 5 tareas en paralelo. Cada tarea debe imprimir su ID y dormir durante 1 segundo.
Solución:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorExercise { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { executor.submit(new Task(i)); } executor.shutdown(); } } class Task implements Runnable { private int taskId; public Task(int id) { this.taskId = id; } @Override public void run() { System.out.println("Task " + taskId + " is running"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
Ejercicio 2: Uso de CountDownLatch
Descripción: Crea un programa que utilice CountDownLatch
para esperar a que 3 tareas completen su ejecución antes de imprimir "All tasks are completed".
Solución:
import java.util.concurrent.CountDownLatch; public class CountDownLatchExercise { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(new Task(latch)).start(); } latch.await(); System.out.println("All tasks are completed"); } } class Task implements Runnable { private CountDownLatch latch; public Task(CountDownLatch latch) { this.latch = latch; } @Override public void run() { System.out.println("Task is running"); latch.countDown(); } }
Conclusión
En esta sección, hemos explorado varias utilidades de concurrencia en Java, incluyendo ExecutorService
, Callable
, Future
, ScheduledExecutorService
, CountDownLatch
, CyclicBarrier
, Semaphore
, y Exchanger
. Estas herramientas son esenciales para escribir programas concurrentes eficientes y seguros. Asegúrate de practicar con los ejemplos y ejercicios proporcionados para consolidar tu comprensión de estos conceptos.
Curso de Programación en Java
Módulo 1: Introducción a Java
- Introducción a Java
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Operadores
Módulo 2: Flujo de Control
Módulo 3: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Métodos
- Constructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
Módulo 4: Programación Orientada a Objetos Avanzada
Módulo 5: Estructuras de Datos y Colecciones
Módulo 6: Manejo de Excepciones
- Introducción a las Excepciones
- Bloque Try-Catch
- Throw y Throws
- Excepciones Personalizadas
- Bloque Finally
Módulo 7: Entrada/Salida de Archivos
- Lectura de Archivos
- Escritura de Archivos
- Flujos de Archivos
- BufferedReader y BufferedWriter
- Serialización
Módulo 8: Multihilo y Concurrencia
- Introducción al Multihilo
- Creación de Hilos
- Ciclo de Vida de un Hilo
- Sincronización
- Utilidades de Concurrencia
Módulo 9: Redes
- Introducción a las Redes
- Sockets
- ServerSocket
- DatagramSocket y DatagramPacket
- URL y HttpURLConnection