Programación Avanzada con Hilos en Java

La programación avanzada con hilos en Java implica no solo la creación y ejecución básica de hilos, sino también el manejo eficiente de recursos compartidos, la coordinación entre múltiples hilos y el uso de herramientas avanzadas como ExecutorService y Callable. En este artículo exploraremos técnicas avanzadas para trabajar con hilos en Java.


1. Conceptos Avanzados sobre Hilos

1.1 Condiciones de Carrera

Ocurren cuando dos o más hilos acceden simultáneamente a un recurso compartido sin sincronización adecuada, lo que puede llevar a resultados impredecibles.

Ejemplo de Condición de Carrera

class Contador {
    private int contador = 0;

    public void incrementar() {
        contador++;
        System.out.println(Thread.currentThread().getName() + " - Valor: " + contador);
    }
}

public class Main {
    public static void main(String[] args) {
        Contador contador = new Contador();

        Runnable tarea = contador::incrementar;

        Thread hilo1 = new Thread(tarea);
        Thread hilo2 = new Thread(tarea);

        hilo1.start();
        hilo2.start();
    }
}

Problema: Los valores de contador pueden ser incorrectos debido al acceso concurrente.

1.2 Sincronización con synchronized

Para evitar condiciones de carrera, sincronizamos el acceso a los recursos compartidos.

class Contador {
    private int contador = 0;

    public synchronized void incrementar() {
        contador++;
        System.out.println(Thread.currentThread().getName() + " - Valor sincronizado: " + contador);
    }
}

2. Comunicación entre Hilos

2.1 Métodos wait() y notify()

Estos métodos permiten que los hilos se comuniquen entre sí al compartir un recurso común.

Ejemplo: Productor-Consumidor

class Almacen {
    private int producto = -1;
    private boolean disponible = false;

    public synchronized void producir(int valor) throws InterruptedException {
        while (disponible) {
            wait(); // Espera hasta que el consumidor retire el producto
        }
        producto = valor;
        disponible = true;
        System.out.println("Producido: " + producto);
        notify(); // Notifica al consumidor
    }

    public synchronized int consumir() throws InterruptedException {
        while (!disponible) {
            wait(); // Espera hasta que haya un producto disponible
        }
        disponible = false;
        System.out.println("Consumido: " + producto);
        notify(); // Notifica al productor
        return producto;
    }
}

public class Main {
    public static void main(String[] args) {
        Almacen almacen = new Almacen();

        Thread productor = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    almacen.producir(i);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread consumidor = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    almacen.consumir();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        productor.start();
        consumidor.start();
    }
}

3. Herramientas Avanzadas para Manejo de Hilos

3.1 Uso de ExecutorService

La clase ExecutorService permite gestionar un grupo de hilos de manera eficiente, reutilizando hilos y simplificando la ejecución concurrente de tareas.

Ejemplo: Pool de Hilos

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        ExecutorService ejecutor = Executors.newFixedThreadPool(3); // Pool de 3 hilos

        for (int i = 1; i <= 10; i++) {
            final int tarea = i;
            ejecutor.execute(() -> {
                System.out.println("Tarea " + tarea + " ejecutada por " + Thread.currentThread().getName());
            });
        }

        ejecutor.shutdown(); // Finaliza el pool
    }
}

3.2 Uso de Callable y Future

Callable es similar a Runnable, pero permite devolver un valor o lanzar excepciones. Future se usa para manejar el resultado de una tarea.

Ejemplo con Callable y Future

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Main {
    public static void main(String[] args) throws Exception {
        ExecutorService ejecutor = Executors.newSingleThreadExecutor();

        Callable<Integer> tarea = () -> {
            System.out.println("Calculando...");
            Thread.sleep(2000);
            return 42; // Devuelve un resultado
        };

        Future<Integer> resultado = ejecutor.submit(tarea);

        System.out.println("El resultado es: " + resultado.get()); // Espera el resultado
        ejecutor.shutdown();
    }
}

3.3 Bloqueos Avanzados con ReentrantLock

ReentrantLock es una alternativa a synchronized que proporciona más flexibilidad, como intentar obtener un bloqueo o establecer tiempos de espera.

Ejemplo con ReentrantLock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Contador {
    private int contador = 0;
    private final Lock bloqueo = new ReentrantLock();

    public void incrementar() {
        bloqueo.lock();
        try {
            contador++;
            System.out.println(Thread.currentThread().getName() + " - Contador: " + contador);
        } finally {
            bloqueo.unlock();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Contador contador = new Contador();

        Runnable tarea = contador::incrementar;

        Thread hilo1 = new Thread(tarea);
        Thread hilo2 = new Thread(tarea);

        hilo1.start();
        hilo2.start();
    }
}

4. Problemas Comunes en la Programación Multihilo

  1. Condiciones de Carrera: Acceso no sincronizado a recursos compartidos.
  2. Bloqueos (Deadlocks): Ocurren cuando dos o más hilos se bloquean mutuamente esperando recursos.
  3. Sobrecarga de Hilos: Crear demasiados hilos puede consumir demasiados recursos.
  4. Interrupciones: Manejar correctamente interrupciones con Thread.interrupt().

5. Prácticas Recomendadas

  1. Usa ExecutorService para manejar grupos de hilos en lugar de crearlos manualmente.
  2. Sincroniza recursos compartidos con synchronized o ReentrantLock.
  3. Divide las tareas en bloques pequeños para que los hilos no bloqueen demasiado tiempo.
  4. Siempre usa try-finally al trabajar con bloqueos para garantizar que se liberen correctamente.

Resumen

La programación avanzada con hilos en Java te permite manejar tareas concurrentes de forma eficiente y segura. Desde el uso de ExecutorService y Callable hasta herramientas como ReentrantLock, estas técnicas garantizan que tus aplicaciones sean más robustas y escalables. La clave para dominar estos conceptos es practicar con casos reales y entender cómo manejar los recursos compartidos de manera efectiva.

Deja un comentario

Información básica sobre protección de datos Ver más

  • Responsable: Tomas Gonzalez.
  • Finalidad:  Moderar los comentarios.
  • Legitimación:  Por consentimiento del interesado.
  • Destinatarios y encargados de tratamiento:  No se ceden o comunican datos a terceros para prestar este servicio.
  • Derechos: Acceder, rectificar y suprimir los datos.
  • Información Adicional: Puede consultar la información detallada en la Política de Privacidad.

error: Content is protected !!

Descubre más desde Tomás González: Formador y Desarrollador Web

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo

Este sitio web utiliza cookies, si necesitas más información puedes visitar nuestra política de privacidad    Ver
Privacidad