3

Uso de hilos en Java(parte 8)

Anteriormente hemos visto:

  • Ejecución secuencial.
  • Uso de concurrencia heredando de la clase Thread e implementar la interfaz Runnable.
  • Particionamiento del trabajo entre los hilos.
  • Comparación del tiempo de la ejecución secuencial vs la ejecución paralela.
  • Ejecutores : Explicación, tipos, ventajas y desventajas.
  • Ejemplo de uso de ejecutores.
  • Entrelazado de instrucciones, sus consecuencias y código de ejemplo de entrelazado.
  • Solución del problema e implementación.
  • Explicación de synchronized, relación con los cerrojos y ejemplo de cerrojos.
  • Tipos de datos atómicos.
  • Control de acceso de hilos usando semáforos.
  • Control de sincronización a través del uso de barreras.
  • Autómatas celulares 1D.
  • Tratamiento de imágenes(autómata 2D) con hilos.

Ahora veremos:

  • Limitaciones del método run() y uso de la interfaz Callable con tareas.
  • Implementación de un programa clientes/servidor que solicita/procesa cálculos.

Hasta ahora hemos utilizado el método run() para poder emplear el uso de hilos, este método viene definido por la interfaz Runnable así void run(), al ser un método void no podemos pedirle un resultado y este hecho nos produce una limitación a la hora de desarrollar nuestros programas, anteriormente he usado varias veces una variable static compartida y ahí guardaba los resultados, pero esto es una consecuencia de la limitación de run(), Java provee una interfaz basada en Runnable que esta pensada para devolver datos en un futuro, esta es la interfaz Callable y provee el siguiente método:

publicT call()throws Exception;

Es una interfaz que es genérica y es necesario instanciarla con un tipo de dato(tipo que pensamos devolver tras el procesamiento de la tarea) para poder emplearla, una de las ventajas de Callable es la posibilidad de ser procesadas por ejecutores, los ejecutores a la hora de recibir tareas Callable nos devolverán instancia de la clase Future que gestionarán nuestros resultados(con el método get de la clase Future podemos acceder al resultado) y cada instancia de esta clase englobará una tarea, veamos un ejemplo en el código, imagina un programa para el cálculo del factorial de 40, el código sería de la siguiente forma:

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

publicclass Tarea implements Callable<Double> {

  int numero;

  // En el ctor. indicamos que número queremos calcularpublic Tarea(int n) {
    if (n < 0) { // Cambio de signo en caso de números negativos.
      n = -n;
    }
    this.numero = n;
  }

  publicDoublecall() throws Exception {
    Double resultado = 1.0;
    if (this.numero != 0) { // Si el número es 0 no realiza el bucle y devulve 1.while (this.numero > 1) { // En caso contrario se multiplicar desde N hasta 2
        resultado = resultado * this.numero;
        --this.numero;
      }
    }

    return resultado;
  }

  publicstaticvoid main(String[] args) throws Exception {
    ExecutorService ejecutor = Executors.newFixedThreadPool(2);
    Tarea factorial10 = new Tarea(10);
    Tarea factorial20 = new Tarea(20);
    Future<Double> resultado10 = ejecutor.submit(factorial10); // Recibimos los futures tras enviar las tareas.
    Future<Double> resultado20 = ejecutor.submit(factorial20);
    // Para recuperar el valor usamos el método get().
    System.out.println("El resultado del factorial de 10 es " + resultado10.get());
    System.out.println("El resultado del factorial de 20 es " + resultado20.get());
    ejecutor.shutdown();
  }
}

Ahora vamos a realizar un programa cliente/servidor utilizando RMI(Remote Method Invocation) para ver un escenario de lo explicado, RMI es un sistema de llamadas que se realizan de forma remota a través de una referencia al servidor, para registrar la referencia al servidor se realiza un bind y obtener la referencia al servidor los clientes realizan un lookup. Este mecanismo de Java se basa en tres componentes:

  • Una clase Cliente que definirá el comportamiento de un cliente genérico.
  • Una clase Servidor de la cual se usará solo una instancia que recibirá las peticiones de los distintos clientes.
  • Una interfaz que actuará como intermediario remoto, es decir, los métodos definidos en esta interfaz serán los empleados por los clientes.

Para ilustrar todo esto supongamos que queremos realizar un página web que actué como una calculadora(el servidor) de factoriales, los diferentes usuarios envían solicitudes de cálculos(clientes) y la operación de cálculo viene definida por la interfaz intermediaria:

import java.rmi.Naming;
import java.rmi.RemoteException;

publicclassClienteextendsThread{
  int id, numero;
  static Interfaz referencia;

  // Asignamos un id y un numero aleatorio.publicCliente(int idCliente){
    this.id = idCliente;
    this.numero = (int) (Math.random() * 50);
  }

  // En el run() todos los clientes realizaran peticiones de factoriales para el// servidorpublicvoidrun(){
    try {
      System.out.println("Soy el cliente " + this.id + " y tengo el número " + numero
          + " y el resultado de mi factorial es " + referencia.factorial(this.numero));
    } catch (RemoteException E) {
    }
  }

  // ANTES DE EJECUTAR LOS MAINS ES NECESARIO ACTIVAR EL RMIREGISTRY.publicstaticvoidmain(String[] args)throws Exception {
    // A traves de lookup podemos obtener la referencia compartida del servidor,// para los clientes.
    referencia = (Interfaz) Naming.lookup("//localhost/servidor");
    Cliente clientes[] = new Cliente[10];
    for (int i = 0; i < 10; i++) {
      clientes[i] = new Cliente(i + 1);
    }
    for (int i = 0; i < 10; i++) {
      clientes[i].start();
    }
    for (int i = 0; i < 10; i++) {
      clientes[i].join();
    }

  }
}
import java.rmi.Remote;
import java.rmi.RemoteException;

//La interfaz intermediaría tiene que extender de Remote.publicinterfaceInterfazextendsRemote{
  // TODOS los métodos se la interfaz tienen que lanzar RemoteException.public Double factorial(int numero)throws RemoteException;
}
import java.rmi.Naming;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;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;//El Servidor tiene que extender de UnicastRemoteObject para que sea aceptado por bind().
public class Servidor extends UnicastRemoteObject implements Interfaz {
  // TODOS los métodos del servidor(menos main) tienen que lanzar RemoteException
  ExecutorService ejecutor;

  // Es necesario implementar un ctor. que llame al método super().
  public Servidor() throws RemoteException {
    super();
    ejecutor = Executors.newCachedThreadPool();
  }

  public Double factorial(int numero) throws RemoteException {
    // El servidor crea una tarea, recibe el Future y devuelve su resultado.
    Factorial tarea = new Factorial(numero);
    Future<Double> resultado = ejecutor.submit(tarea);
    Double output = null;
    try {
      output = resultado.get();
    } catch (InterruptedException | ExecutionException E) {
    }
    return output;
  }

  // ANTES DE EJECUTAR LOS MAINS ES NECESARIO ACTIVAR EL RMIREGISTRY.// Tanto el cliente como el servidor tienen que implementar su propio main.
  public static void main(String[] args) throws Exception {
    Servidor servidor = new Servidor();
    // Para que el cliente pueda hacer lookup es necesario que el servidor haya// realizado previamente el bind.
    Naming.bind("//localhost/servidor", servidor);
    System.out.println("Servidor listo para recibir peticiones!");
  }
}
import java.util.concurrent.Callable;

//Clase auxiliar para el servidorpublicclass Factorial implements Callable<Double> {
  int numero;

  public Factorial(int n) {
    this.numero = n;
  }

  publicDoublecall() {
    // En lugar de crear una clase que implemente Callable vamos a usar instancias.Double factorial = 1.0;
    if (this.numero != 0) {
      while (this.numero > 1) {
        factorial = factorial * this.numero;
        this.numero--;
      }
    }
    return factorial;
  }
}

Para la ejecución habría que ejecutar en orden:

  • rmiregistry &
  • java Servidor
  • java Cliente

La salida que ofrece es la siguiente:
rmi.png

Escribe tu comentario
+ 2