Programación multihilo ========================== Recursos compartidos por los hilos. ---------------------------------------------------------------- Cuando creamos varios objetos de una clase, puede ocurrir que varios hilos de ejecución accedan a un objeto. Es importante recordar que **todos los campos del objeto son compartidos entre todos los hilos**. Supongamos una clase como esta: .. code-block:: java public class Empleado(){ int numHorasTrabajadas=0; public void int incrementarHoras(){ numHorasTrabajadas++; } } Si varios hilos ejecutan sin querer el método ``incrementar`` en teoría debería ocurrir que el número se incrementase tantas veces como procesos. Sin embargo, **es muy probable que eso no ocurra** Estados de un hilo. Cambios de estado. -------------------------------------------------------------------------- Aunque no lo vemos, un hilo cambia de estado: puede pasar de la nada a la ejecución. De la ejecución al estado "en espera". De ahí puede volver a estar en ejecución. De cualquier estado se puede pasar al estado "finalizado". El programador no necesita controlar esto, lo hace el sistema operativo. Sin embargo un programa multihilo mal hecho puede dar lugar problemas como los siguientes: * Interbloqueo. Se produce cuando las peticiones y las esperas se entrelazan de forma que ningún proceso puede avanzar. * Inanición. Ningún proceso consigue hacer ninguna tarea útil y por lo tanto hay que esperar a que el administrador del sistema detecte el interbloqueo y mate procesos (o hasta que alguien reinicie el equipo). Elementos relacionados con la programación de hilos. Librerías y clases. -------------------------------------------------------------------------- Para crear programas multihilo en Java se pueden hacer dos cosas: 1. Heredar de la clase Thread. 2. Implementar la interfaz Runnable. Los documentos de Java aconsejan el segundo. Lo único que hay que hacer es algo como esto. .. code-block:: java class EjecutorTareaCompleja implements Runnable{ private String nombre; int numEjecucion; public EjecutorTareaCompleja(String nombre){ this.nombre=nombre; } @Override public void run() { String cad; while (numEjecucion<100){ for (double i=0; i<4999.99; i=i+0.04) { Math.sqrt(i); } cad="Soy el hilo "+this.nombre; cad+=" y mi valor de i es "+numEjecucion; System.out.println(cad); numEjecucion++; } } } public class LanzaHilos { /** * @param args */ public static void main(String[] args) { int NUM_HILOS=500; EjecutorTareaCompleja op; for (int i=0; i8000){ Thread hilo=Thread.currentThread(); System.out.println( "Terminando hilo:"+ hilo.getName() ); hilo.join(); } Thread.sleep(tiempo); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("Calculado y reiniciando."); num++; } Thread hilo=Thread.currentThread(); String miNombre=hilo.getName(); System.out.println("Hilo terminado:"+miNombre); } } public class LanzadorHilos { public static void main(String[] args) { Calculador vHilos[]=new Calculador[5]; for (int i=0; i<5;i++){ vHilos[i]=new Calculador(); Thread hilo=new Thread(vHilos[i]); hilo.setName("Hilo "+i); if (i==0){ hilo.setPriority(Thread.MAX_PRIORITY); } hilo.start(); } } } Ejercicio: crear un programa que lance 10 hilos de ejecución donde a cada hilo se le pasará la base y la altura de un triángulo, y cada hilo ejecutará el cálculo del área de dicho triángulo informando de qué base y qué altura recibió y cual es el área resultado. Una posibilidad (quizá incorrecta) sería esta: Para implementar la clase ``CalculadorAreas`` .. literalinclude:: ../listados/Clase_com_ies_CalculadorAreas.java :language: java Para implementar la clase ``AreasEnParalelo`` que lanza los hilos para hacer muchos cálculos de áreas en paralelo usaremos esto: .. literalinclude:: ../listados/Clase_com_ies_AreasEnParalelo.java :language: java .. code-block:: java package com.ies; import java.util.Random; class CalculadorAreas implements Runnable{ int base, altura; public CalculadorAreas(int base, int altura){ this.base=base; this.altura=altura; } @Override public void run() { float area=this.base*this.altura/2; System.out.print("Base:"+this.base); System.out.print("Altura:"+this.altura); System.out.println("Area:"+area); } } public class AreasEnParalelo { public static void main(String[] args) { Random generador=new Random(); int numHilos=10000; int baseMaxima=3; int alturaMaxima=5; for (int i=0; i300){ System.out.println("Este hilo trabaja mucho"); } } } Problema: filósofos ------------------------------------------------------ En una mesa hay procesos que simulan el comportamiento de unos filósofos que intentan comer de un plato. Cada filósofo tiene un cubierto a su izquierda y uno a su derecha y para poder comer tiene que conseguir los dos. Si lo consigue, mostrará un mensaje en pantalla que indique "Filosofo 2 comiendo". Despues de comer, soltará los cubiertos y esperará al azar un tiempo entre 1000 y 5000 milisegundos, indicando por pantalla "El filósofo 2 está pensando". En general todos los objetos de la clase Filósofo está en un bucle infinito dedicándose a comer y a pensar. Simular este problema en un programa Java que muestre el progreso de todos sin caer en problemas de sincronización ni de inanición. .. figure:: ../imagenes/Filosofos.png :figwidth: 50% :align: center Esquema de los filósofos Boceto de solución ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: java import java.util.Random; public class Filosofo implements Runnable{ public void run(){ String miNombre=Thread.currentThread().getName(); Random generador=new Random(); while (true){ /* Comer*/ /* Intentar coger palillos*/ /* Si los coge:*/ System.out.println(miNombre+" comiendo..."); int milisegs=(1+generador.nextInt(5))*1000; esperarTiempoAzar(miNombre, milisegs); /* Pensando...*/ //Recordemos soltar los palillos System.out.println(miNombre+" pensando..."); milisegs=(1+generador.nextInt(5))*1000; esperarTiempoAzar(miNombre, milisegs); } } private void esperarTiempoAzar(String miNombre, int milisegs) { try { Thread.sleep(milisegs); } catch (InterruptedException e) { System.out.println( miNombre+" interrumpido!!. Saliendo..."); return ; } } } Solución completa al problema de los filósofos ------------------------------------------------------ Gestor de recursos compartidos (palillos) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: java package com.iesmaestre.filosofos; public class GestorPalillos { boolean palilloLibre[]; public GestorPalillos(int numPalillos){ palilloLibre=new boolean[numPalillos]; for (int i=0; i cola; public Cola (int max){ cola=new LinkedList(); this.MAX_ELEMENTOS=max; } /* En realidad, si estamos seguro de que nadie llamará a este método podriamos ponerla como no synchronized*/ public synchronized boolean estaVacia(){ int numElementos=cola.size(); if (numElementos==0){ return true; } return false; } /* Igual que antes, si estamos seguro de que nadie llamará a este método podriamos ponerla como no synchronized*/ public synchronized boolean estaLlena(){ int numElementos=cola.size(); if (numElementos==this.MAX_ELEMENTOS){ return true; } return false; } /* Devuelve true si se pudo hacer y false si no se pudo*/ public synchronized boolean encolar(int numero){ if (estaLlena()){ return false; } cola.addLast(numero); return true; } public synchronized int desencolar(){ /* Necesitamos un número especial que actúe como comprobador de errores*/ if (estaVacia()){ return -1; } int numero=cola.removeFirst(); return numero; } } La clase Productor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: java public class Productor implements Runnable{ Cola colaCompartida; public Productor(Cola cola){ this.colaCompartida=cola; } public void run() { while (true){ int num=Utilidades.numAzar(10); while (colaCompartida.encolar(num)==false){ Utilidades.esperarTiempoAzar(3000); } /*Fin del while*/ System.out.println("Productor encoló el numero:"+num); } /*Fin del while externo*/ } /*Fin de run()*/ } /*Fin de la clase*/ La clase Consumidor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: java public class Consumidor implements Runnable{ Cola colaCompartida; public Consumidor(Cola cola){ this.colaCompartida=cola; } @Override public void run() { int num; while (true){ num=colaCompartida.desencolar(); if (num!=-1){ System.out.println("Consumidor recuperó el numero:"+num); } /* Fin del if*/ } /*Fin del bucle infinito*/ } /* Fin del run()*/ } /*Fin de la clase Consumidor*/ Un lanzador ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: java public class Lanzador { public static void main(String[] args) throws InterruptedException { int MAX_PRODUCTORES = 5; int MAX_CONSUMIDORES = 7; int MAX_ELEMENTOS = 10; Thread[] hilosProductor; Thread[] hilosConsumidor; hilosProductor = new Thread[MAX_PRODUCTORES]; hilosConsumidor = new Thread[MAX_CONSUMIDORES]; Cola colaCompartida=new Cola(MAX_ELEMENTOS); /*Construimos los productores*/ for (int i=0; i0){ this.numProductos--; return true; } return false; } } Clase Puerta ~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: java package grandesalmacenes; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; public class Puerta { boolean ocupada; Puerta(){ this.ocupada=false; } public boolean estaOcupada(){ return this.ocupada; } public synchronized void liberarPuerta(){ this.ocupada=false; } public synchronized boolean intentarEntrar(){ if (this.ocupada) return false; /* Si llegamos aquí, la puerta estaba libre pero la pondremos a ocupada un tiempo y luego la volveremos a poner a "libre"*/ this.ocupada=true; return true; } } Clase lanzadora (GrandesAlmacenes) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: java package grandesalmacenes; public class GrandesAlmacenes { public static void main(String[] args) throws InterruptedException { final int NUM_CLIENTES = 300; final int NUM_PRODUCTOS = 100; Cliente[] cliente = new Cliente[NUM_CLIENTES]; Almacen almacen = new Almacen(NUM_PRODUCTOS); Puerta puerta = new Puerta(); Thread[] hilosAsociados=new Thread[NUM_CLIENTES]; for (int i=0; i{ int[] numeros; int pos1, pos2; final int NUM_MAX_ELEMENTOS=2; private int longitud; private int posMitad; private int limiteIzquierdo; private int limiteDerecho; /*Por simplificar solo aceptamos vectores con un número de elementos que sea una potencia de 2*/ public Sumador(int[] numeros, int pos1, int pos2) { this.numeros = numeros; this.pos1 = pos1; this.pos2 = pos2; longitud = 1 + (pos2- pos1); posMitad = longitud/2; limiteIzquierdo = pos1+(posMitad-1); limiteDerecho = limiteIzquierdo+1; } @Override protected Long compute() { /* Si el vector tiene estos elementos, simplemente sumamos*/ if (longitud==NUM_MAX_ELEMENTOS){ long suma=0; for (int i=pos1; i<=pos2; i++){ suma=suma + numeros[i]; } return suma; } /*Pero si tiene más elementos, dividimos el vector en dos y sumamos las dos mitades de forma independiente y paralela*/ Sumador sumadorIzq= new Sumador(numeros, pos1, limiteIzquierdo); Sumador sumadorDer= new Sumador(numeros, limiteDerecho, pos2); /*Lanzamos los sumadores...*/ sumadorIzq.fork(); sumadorDer.fork(); /* Y recogemos sus resultados*/ long sumaIzq=sumadorIzq.join(); long sumaDer=sumadorDer.join(); return sumaIzq + sumaDer; } /*Fin del compute*/ } /*Fin de la clase*/ Con esta clase como "lanzador de hilos", nuestro trabajo se simplifica bastante. Ahora podemos tener un ``main`` que va a ser tan sencillo como el código siguiente: .. code-block:: java package io.github.oscarmaestre.paralelismo; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; public class SumadorParalelo { public static void main(String[] args) throws InterruptedException, ExecutionException { /* Construimos un vector de ejemplo...*/ final int MAX_NUMEROS=1024; int[] numeros=new int[MAX_NUMEROS]; /*Y lo rellenamos con números*/ for (int i=0; i