Multitarea en Java Rafa Caballero - UCM
Programa Monoproceso (monotarea) En cada momento hay una única instrucción ejecutándose Se dice que el programa es monotarea, o monoproceso o monohebra (o single threading).
Programa multiproceso En algún punto el programa se divide en varios procesos (threads) que se ejecutan (aparentemente) de manera simultánea Programa multiproceso, multitarea, multihebra (o multithreading)
Para qué? Programas que tengan que realizar varias tareas de manera simultánea Programas en los que la ejecución de una parte requiera tiempo y no deba detener el resto del programa
Rafa Caballero - U Para qué? Ejemplo 1 Programa que controla sensores en una fábrica: Los procesos que se encargan de controlar sensores diferentes son independientes y los sensores deben controlarse de manera simultánea Temperatura Combustible Programa Presión Tiempo
Para qué? Ejemplo 2 Durante la impresión de un documento (tarea que puede tomar tiempo) el programa puede y debe continuar ejecutándose. Programa
Multitarea en Java Idea: Definir clases especiales para las tareas que requieran ejecutarse en una hebra de ejecución (thread) separada Estas clases deben incluir la función especial public void run(), equivalente a main() pero para hebras Los objetos de la clase serán inicializados desde otra clase con el método start()
Rafa Caballero - U Clase para multitarea 2 Métodos: Mediante herencia (extensión) de la clase java.lang.thread Mediante la implementación del interfaz java.lang.runnable
Ejemplo: extensión de Thread El programa irá escribiendo dos sucesiones ascendentes de números simultáneamente Una hebra por cada contador
Ejemplo: Contadores (Versión 1) class Contador extends Thread { private int inicio,fin; // valor inicial y final del contador private String nombre; // nombre de la hebra // constructor con los valores iniciales y final y el nombre public Contador(int desde, int hasta, String id) { inicio = desde; fin = hasta; nombre = id; } // función principal public void run() { System.out.println(nombre+ " empieza..."); for (int i = inicio; i <= fin; i++) { System.out.print (nombre+" dice: "+i + ". "); try{ sleep(10); // paramos una centesima de segundo } catch (InterruptedException e) { e.printstacktrace(); } } System.out.println(nombre + " acaba."); } Rafa Caballero - U
Ejemplo: Contadores (Versión 1) // clase principal: esta clase inicializara las hebras public class principal { static public void main(string[] args) { // al declararlas NO comienzan Contador hebraa = new Contador(1, 10, "HebraA"); Contador hebrab = new Contador(20, 30, "HebraB"); System.out.println("Vamos a iniciar las dos hebras"); // ahora comienzan hebraa.start(); hebrab.start(); System.out.println("Hebras inicializadas"); // hacemos un poco de tiempo antes de despedirnos for (int i=0; i<50000000; i++) ; System.out.println("Programa principal terminado"); } } // principal
Ejemplo: contadores Una ejecución del programa: Vamos a iniciar las dos hebras Hebras inicializadas HebraA empieza... HebraA dice: 1. HebraB empieza... HebraB dice: 20. HebraA dice: 2. HebraB dice: 21. HebraA dice: 3. HebraB dice: 22. HebraA dice: 4. Programa principal terminado HebraB dice: 23. HebraA dice: 5. HebraB dice: 24. HebraA dice: 6. HebraB dice: 25. HebraA dice: 7. HebraB dice: 26. HebraA dice: 8. HebraB dice: 27. HebraA dice: 9. HebraB dice: 28. HebraA dice: 10. HebraB dice: 29. HebraA acaba. HebraB dice: 30. HebraB acaba.
Ejemplo: contadores Vamos a iniciar las dos hebras Hebras Inicializadas HebraA empieza HebraA dice:1 HebraA dice:2 HebraA dice:3 HebraA dice:4 HebraA dice:5 HebraA dice:6 HebraA dice:7 HebraA dice:8 HebraA dice:9 HebraA dice:10 HebraA acaba Programa Principal Terminado Hebra B empieza HebraB dice: 20 HebraB dice: 21 HebraB dice: 22 HebraB dice: 23 HebraB dice: 24 HebraB dice: 25 HebraB dice: 26 HebraB dice: 27 HebraB dice: 28 HebraB dice: 29 HebraB acaba
Rafa Caballero - U Ejemplo: Interfaz Runnable Se utiliza generalmente cuando la clase ya hereda de otra clase y por tanto no puede heredar de Thread class Hebra extends loquesea implements Runnable { }
Ejemplo: Contadores (versión 2) class Contador implements Runnable { private int inicio,fin; // valor inicial y final del contador private String nombre; // nombre de la hebra // constructor con los valores iniciales y final y el nombre public Contador(int desde, int hasta, String id) { inicio = desde; fin = hasta; nombre = id; } public void run() { System.out.println(nombre+ " empieza..."); for (int i = inicio; i <= fin; i++) { System.out.print (nombre+" dice: "+i + ". "); try{ Thread.sleep(10); // paramos una centesima de segundo } catch (InterruptedException e) { e.printstacktrace(); } } System.out.println(nombre + " acaba."); } Rafa Caballero - U
Ejemplo: Contadores (versión 2) // clase principal: esta clase inicializara las hebras public class principal { static public void main(string[] args) { // al declararlas NO comienzan Thread Contador hebraa hebraa = new = new Thread Contador(1, (new Contador(1, 10, "HebraA"); 10, "HebraA")); Thread Contador hebrab hebrab = new = Thread new Contador(20, (new Contador(20, 30, "HebraB"); 30, "HebraB")); // ahora comienzan hebraa.start(); hebrab.start(); System.out.println("Hebras inicializadas"); // hacemos un poco de tiempo antes de despedirnos for (int i=0; i<50000000; i++) ; System.out.println("Programa principal terminado"); } } // principal
Comunicación entre hebras La forma de comunicarse consiste usualmente en compartir un mismo objeto Generalmente el objeto se pasa como parámetro en la constructora de la clase hebra
Ejemplo: Juego para adivinar un número 4 Árbitro Número: 5 3 2 7 Jugador Rafa Caballero - U
Rafa Caballero - U Ejemplo: Juego para adivinar un número 3 Clases: Principal: Inicializa el árbitro y lanza las hebras de los jugadores Árbitro: Contiene el número a adivinar, el turno y muestra el resultado Jugador: Extiende Thread e incluye al árbitro.
Rafa Caballero - U Ejemplo: Juego para adivinar un número public class Principal { public static void main(string[] args) { // creamos el árbitro y los jugadores Arbitro arbitro = new Arbitro(3); // 3 jugadores Jugador j1 = new Jugador(1,arbitro); Jugador j2 = new Jugador(2,arbitro); Jugador j3 = new Jugador(3,arbitro); // ponemos a los jugadores en marcha j1.start(); j2.start(); j3.start(); } }
Rafa Caballero - U Ejemplo: Juego para adivinar un número class Arbitro { private int totaljugadores; // núm. de jugadores private int turno; // a quién le toca private int numero; // número a adivinar private boolean acabo; // true cuando se haya terminado el juego public Arbitro(int njugadores) {// constructora totaljugadores = njugadores; turno = 1+(int) (totaljugadores*math.random()); numero = 1+(int) (10*Math.random()); // número entre 1 y 10 acabo = false; } public int toca() { return turno; } public boolean seacabo() { return acabo; } public synchronized void nuevajugada(int jugador, int sunumero) { } }
Ejemplo: Juego para adivinar un número public synchronized void nuevajugada(int jugador, int sunumero){ if (jugador == toca()) { // ha acertado System.out.println( Jugador +jugador+" dice: "+sunumero); if (sunumero == numero) { System.out.println( Jugador +jugador + " gana!!!"); acabo = true; } else // ha fallado. ver a quien le toca ahora if (turno == totaljugadores) turno = 1; else turno++; } else System.out.println(jugador+" trata de hacer trampa!"); }
Ejemplo: Juego para adivinar un número class Jugador extends Thread { Arbitro arbitro; int identificador; public Jugador( int elid,arbitro elarbitro) { arbitro = elarbitro; identificador = elid; } public void run() { while (arbitro.seacabo() == false) { // hasta el fin del juego if (arbitro.toca()==identificador) { // es nuestro turno int jugada = 1+(int) (10*Math.random()); arbitro.nuevajugada(identificador,jugada); } // if } // while } // run }
Rafa Caballero - U Ejemplo: Juego para adivinar un número Una ejecución del programa: Jugador 3 dice: 9 Jugador 1 dice: 2 Jugador 2 dice: 6 Jugador 3 dice: 7 Jugador 1 dice: 2 Jugador 2 dice: 5 Jugador 2 gana!!!
Ejemplo: Juego para adivinar un número Observaciones: Aunque cada hebra tenga su variable árbitro todas son referencias al mismo objeto Con synchronized se protegen aquellas funciones que no se quiera que se puedan interrumpir por otra hebra
Ejercicio: Control de stock Se sabe que a un almacén llegan piezas cada 8 horas. La cantidad de piezas oscila cada vez entre 400 y 1000 Del almacén salen cada 24 horas piezas hacia la fábrica, a un ritmo de entre 2000 y 2500 piezas (todas a la vez) El almacén parte de 8000 piezas y tiene una capacidad máxima de 20000 piezas Rafa Caballero
Rafa Caballero - U Ejercicio: Control de stock El programa debe simular este proceso parando si: Llega un nuevo cargamento y ya no cabe en el almacén La fábrica necesita piezas pero no hay suficientes piezas en el almacén
Ejercicio: Control de stock
Rafa Caballero - U Ejercicio: Control de stock Vamos a hacer la simulación con 4 clases: Retirada: Simula retirada de piezas hacia la fábrica. Además escribe el número de días transcurridos desde el comienzo Envío: Simula el envío de piezas al almacén Almacén: Simula el almacén. Tendrá funciones para atender las llegadas y las salidas de piezas. Controlará si hay algún error y mostrará mensajes con el movimiento del almacén Principal: Función main que pondrá en marcha la aplicación
Rafa Caballero - U Ejercicio: Control de stock Envío y Retirada heredarán de la clase Thread y compartirán el objeto tipo Almacén. Ambas pararán cuando el Almacén indique que hay un error (bien porque no hay piezas para atender un pedido o porque no caben más piezas y hay un envío)
Rafa Caballero - U Ejercicio: Control de stock Para simular el tiempo, las funciones run() de las clases Retirada y Envio incluirán sendas llamadas a sleep: En el caso de Retirada: try{ sleep(2400); // simulación de un día } catch (InterruptedException e) { e.printstacktrace(); } En el caso de Envio: try{ sleep(800); // simulación de 8 horas } catch (InterruptedException e) { e.printstacktrace(); } De está manera habrá 3 envíos por cada retirada
Rafa Caballero - U Ejercicio: Control de stock // Estructura de la clase almacén: class Almacen { private final int maximo=20000; // capacidad del almacén private int stock = 8000; // núm. Piezas en el almacén. Al principio 8000 private boolean hayerror = false; // al principio no hay error // Método entrada: carga es la cantidad de piezas que llegan al almacén // si la carga + el stock superan el maximo mostrará un mensaje de error y // pondrá hayerrora true. En otro caso incrementará el stock con la carga public void entrada(int carga) {.} // Método salida: piezas es la cantidad de piezas pedida por la fábrica // Si stock < piezas se mostrará un mensaje de erro y se pondrá // hayerror a true. En otro caso se decrementará el stock en piezas public void salida(int piezas) { } // pedido public boolean error() { return hayerror; } }
Ejercicio: Control de stock Día 1 Pedido de 2179 piezas Hay 5821 piezas en el almacén Llegan 744 piezas Hay 6565 piezas en el almacén Llegan 580 piezas Hay 7145 piezas en el almacén Llegan 624 piezas Hay 7769 piezas en el almacén Día 2 Pedido de 2420 piezas Hay 5349 piezas en el almacén... Día 41 Pedido de 2409 piezas Hay 699 piezas en el almacén Llegan 586 piezas Hay 1285 piezas en el almacén Llegan 404 piezas Hay 1689 piezas en el almacén Llegan 462 piezas Hay 2151 piezas en el almacén Día 42 Pedido de 2483 piezas No hay piezas suficientes!
Rafa Caballero - U Ejercicio: Control de stock Resultados de 20 simulaciones 80 70 60 50 40 30 20 10 0 1ª 4ª 7ª 10ª 13ª 16ª 19ª Días hasta quedarse vacío
Ejercicio: Control de stock Sugerencia: probar con otros valores Por ejemplo, con 4 envíos por día se comprobará que el almacén se llena en aproximadamente 25 días