Programación Multihilo en Java

Introducción a los Hilos:

  • Un hilo es una vía de ejecución independiente
  • Los programas en ejecución tienen muchos procesos en segundo plano como el hilo principal, young.gc, full.gc()
  • main es el hilo principal, punto de entrada del sistema, utilizado para ejecutar todo el programa
  • En un proceso, si se abren múltiples hilos, la ejecución de los hilos es gestionada por el programador, cuyo orden no puede ser intervenido por el humano
  • Al operar sobre el mismo recurso, existen problemas de competencia de recursos, que requieren control de concurrencia
  • Los hilos traen sobrecargas adicionales, como el tiempo de programación de CPU y la sobrecarga del control de concurrencia
  • Cada hilo interactúa en su propia memoria de trabajo, y un control inadecuado de la memoria puede causar inconsistencia de datos

Implementación de Hilos:

Extensión de la clase Thread


/**
* Ejemplo de extensión de la clase Thread
*/
public class EjemploHilo extends Thread {
   // Punto de entrada del hilo
   @Override
   public void run() {
       // Cuerpo del hilo
       for (int i = 0; i < 10; i++) {
           System.out.println("Procesando imagen " + i);
       }
   }
   
   public static void main(String[] args) {
       EjemploHilo hiloPrueba = new EjemploHilo();
       hiloPrueba.start(); // Inicia el hilo, no necesariamente inmediatamente, requiere programación de CPU
       hiloPrueba.run(); // Ejecuta inmediatamente el hilo
       
       for (int i = 0; i < 10; i++) {
           System.out.println("Ejecutando el método main " + i);
       }
   }
}
       

Descarga de imágenes con Thread


public class DescargaImagen extends Thread {
   private String url;
   private String nombreArchivo;

   public DescargaImagen(String url, String nombreArchivo) {
       this.url = url;
       this.nombreArchivo = nombreArchivo;
   }
   
   @Override
   public void run() {
       DescargadorWeb descargador = new DescargadorWeb();
       descargador.descargar(url, nombreArchivo);
       System.out.println("Descargado archivo: " + nombreArchivo);
   }
   
   public static void main(String[] args) {
       DescargaImagen hilo1 = new DescargaImagen("https://ejemplo.com/imagen1.jpg", "foto1.jpg");
       DescargaImagen hilo2 = new DescargaImagen("https://ejemplo.com/imagen2.jpg", "foto2.jpg");
       DescargaImagen hilo3 = new DescargaImagen("https://ejemplo.com/imagen3.jpg", "foto3.jpg");

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

class DescargadorWeb {
   public void descargar(String url, String nombreArchivo) {
       try {
           FileUtils.copyURLToFile(new URL(url), new File(nombreArchivo));
           System.out.println("Descarga completada");
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}
       

Implementación de la interfaz Runnable


public class ImplementacionRunnable implements Runnable {
   public static void main(String[] args) {
       ImplementacionRunnable runnable = new ImplementacionRunnable();
       new Thread(runnable).run();

       for (int i = 0; i < 10; i++) {
           System.out.println("Procesando en el hilo principal " + i);
       }
   }

   @Override
   public void run() {
       for (int i = 0; i < 10; i++) {
           System.out.println("Ejecutando tareas en segundo plano " + i);
       }
   }
}
       

Problemas de Concurrencia Inicial


/**
* Ejemplo de implementación de la interfaz Runnable
* La ejecución del hilo requiere envolverlo en new Thread(ImplementacionRunnable)
* Se recomienda oficialmente la implementación de la interfaz Runnable
*/
public class SistemaReservas implements Runnable {

   // Simulación de asientos disponibles
   private int asientosDisponibles = 100;
   
   @Override
   public void run() {
       while (true) {
           // Salir cuando no queden asientos
           if (asientosDisponibles <= 0) {
               break;
           }
           // Simular demora
           try {
               Thread.sleep(10);
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
           // Bloquear el acceso a los asientos
           synchronized ((Object)asientosDisponibles) {
               // Verificar nuevamente
               if (asientosDisponibles <= 0) {
                   break;
               }
               System.out.println(Thread.currentThread().getName() + " reservó el asiento número " + asientosDisponibles--);
           }
       }
   }

   public static void main(String[] args) {
       SistemaReservas sistema = new SistemaReservas();

       new Thread(sistema, "Cliente1").start();
       new Thread(sistema, "Cliente2").start();
       new Thread(sistema, "Cliente3").start();
   }
}
       

Carrera entre Tortuga y Conejo


public class Carrera implements Runnable{
   private static String Ganador;
   
   // Contenido de la carrera
   public void run(){
       // Si el hilo es el conejo, hacer que descanse 20ms
       if(Thread.currentThread().getName().equals("Conejo")){
           try{
               Thread.sleep(20);
           }catch(RuntimeException | InterruptedException e){
               e.printStackTrace();
           }
       }
       
       for(int i = 1; i <= 10; i++){
           System.out.println(Thread.currentThread().getName() + " avanzó " + i + " pasos");
           // Verificar si la carrera ha terminado
           Boolean terminada = verificarFin(i);
           if (terminada){
               break;
           }
       }
   }

   public static void main(String[] args){
       Carrera carrera = new Carrera();
       new Thread(carrera, "Conejo").start();
       new Thread(carrera, "Tortuga").start();
   }
   
   public Boolean verificarFin(int paso){
       // Si hay un ganador, detener la carrera
       if(Ganador != null){
           return true;
       }
       // Si alguien ha llegado a 10 pasos, detener la carrera
       if(paso == 10){
           Ganador = Thread.currentThread().getName();
           System.out.println("El ganador es " + Ganador);
           return true;
       }
       return false;
   }
}
       

Interfaces Funcionales y Expresiones Lambda

Antes de Java 8


public class EjemploLambda {
   // Una interfaz con un solo método se llama interfaz funcional
   interface ILike {
       void like(int a);
   }

   public static void main(String[] args){
       ILike like = new LikeImpl();
       like.like(521);
   }

   static class LikeImpl implements ILike {
       @Override
       public void like(int a){
           System.out.println("Me gustas " + a);
       }
   }
} 
       

Simplificación 1: Clase Interna Estática


public class EjemploLambda2{
   // Una interfaz con un solo método se llama interfaz funcional
   interface ILike {
       void like(int a);
   }

   public static void main(String[] args){
       ILike like = new LikeImpl();
       like.like(521);
   }
   
   static class LikeImpl implements ILike {
       @Override
       public void like(int a){
           System.out.println("Me gustas " + a);
       }
   }
}
       

Simplificación 2: Clase Interna Local


public class EjemploLambda3{
   // Una interfaz con un solo método se llama interfaz funcional
   interface ILike {
       void like(int a);
   }

   public static void main(String[] args){
       class LikeImpl implements ILike{
           @Override
           public void like(int a){
               System.out.println("Me gustas " + a);
           }
       }
       ILike like = new LikeImpl();
       like.like(521);
   }
}
       

Simplificación 3: Clase Aónima


interface ILike {
   void like(int a);
}

public class EjemploLambda4{
   // Una interfaz con un solo método se llama interfaz funcional

   public static void main(String[] args){
       ILike like = new ILove(){
           @Override
           public void like(int a){
               System.out.print("Me gustas " + a);
           }
       };
       like.like(521);
   }
}
       

Versión Final con Lambda


interface ILike {
   void like(int a, int b);
}

public class EjemploLambdaFinal {
   // Una interfaz con un solo método se llama interfaz funcional
   public static void main(String[] args) {
       ILike like = (a,b) -> {
           System.out.println("Me gustas " + a + b);
       };
       like.like(520, 521);
   }
}
       

Resumen de Lambda

  • La interfaz solo puede tener un método
  • Al ejecutar la lógica de negocio con lambda, deben llevar {}, a menos que la lógica sea solo una línea
  • Se pueden eliminar los tipos de parámetro (todos) cuando hay múltiples parámetros

Estados del Hilo:

Creación: new Thread()

Listo (esperando programación de CPU): start()

Bloqueado (después de bloquearse, espera programación de CPU): sleep(), wait() o bloqueo síncrono

Sleep


/**
* Ejemplo con sleep para cuenta regresiva
*/

public class EjemploSleep{
   public static void main(String[] args) throws InterruptedException {
       cuentaRegresiva();
   }

   static void cuentaRegresiva() throws InterruptedException {
       int num = 10;
       while (true){
           Thread.sleep(1000);
           System.out.println( num-- );
           if (num <= 0){
               break;
           }
       }
   }
}
       

Join: hace que un hilo se bloquee, similar a un VIP que ejecuta primero


/**
* Ejemplo de join, hace que un hilo se bloquee
*/

public class EjemploJoin implements Runnable{

   @Override
   public void run() {
       for (int i = 1; i < 101; i++) {
           System.out.println("VIP en acción, todos aparten! " + i);
       }
   }
   
   public static void main(String[] args) throws InterruptedException {
       EjemploJoin ejemploJoin = new EjemploJoin();
       Thread hilo = new Thread(ejemploJoin);
       hilo.start();
       
       for (int i = 1; i < 101; i++) {
           System.out.println("Método principal " + i);
           if(i == 10){
               hilo.join(); // Fuerza la ejecución del método run del hilo
           }
       }
   }
}
       

Ejecución: start(), run()

Destrucción

No se recomienda usar stop(), die() u otros métodos obsoletos o no recomendados oficialmente.

Se recomienda usar métodos basados en banderas para detener hilos:


/*
* Ejemplo de detención de hilo usando banderas
* No se recomienda usar stop(), die() u otros métodos obsoletos
* Se recomienda usar métodos basados en banderas para detener hilos
*/
public class DetencionHilo implements Runnable {
   public Boolean bandera = true;
   
   @Override
   public void run() {
       int i = 0;
       while (bandera) {
           System.out.println("Paso " + i++);
       }
   }

   // Método público para detener
   public void detener() {
       this.bandera = false;
   }

   public static void main(String[] args) {
       DetencionHilo detencionHilo = new DetencionHilo();

       new Thread(detencionHilo).start();

       for (int i = 0; i < 100; i++) {
           System.out.println("Método principal " + i);
           if (i == 90) {
               // Detener el hilo después de 90 ciclos
               detencionHilo.detener();
               System.out.println("El hilo debe detenerse");
           }
       }
   }
}
       

Sincronización de Hilos

La sincronización de hilos es crucial para evitar condiciones de carrera y garantizar la consistencia de datos cuando múltiples hilos acceden a recursos compartidos.

Comunicación entre Hilos

La comunicación entre hilos permite que los hilos coordinen su trabajo y compartan información de manera segura.

Temas Avanzados

Lock en JDK 15

El paquete java.util.concurrent.locks proporciona interfaces y clases más avanzadas para el control de concurrencia que los mecanismos de sincronización tradicionales.

Etiquetas: java multihilo concurrencia hilos Runnable

Publicado el 6-7 02:33