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
