La descarga está en progreso. Por favor, espere

La descarga está en progreso. Por favor, espere

CJ02 – Técnicas avanzadas en java 1.4 y 5.0

Presentaciones similares


Presentación del tema: "CJ02 – Técnicas avanzadas en java 1.4 y 5.0"— Transcripción de la presentación:

1 CJ02 – Técnicas avanzadas en java 1.4 y 5.0
Parte 2 – Hilos en java 5.0

2 Objetivos generales Multitareas Hilos – Procesos Locks Monitores
Deadlocks Ejemplos: productor y consumidor

3 Multitasking Multitasking es lo que permite que varias actividades ocurran concurrentemente en una computadora Habiltualmente se ditinguen: Multitasking basada en procesos Multitasking basada en threads (hilos) Para hablar de hilos en Java se necesita entender qué es un hilo. Un hilo es un proceso que se está ejecutando en un momento determinado en nuestro sistema operativo, como cualquier otra tarea, esto se realiza directamente en el procesador. Existen los llamados 'demonios' que son los procesos que define el sistema en sí para poder funcionar y otros que llamaremos los hilos definidos por el usuario o por el programador, estos últimos son procesos a los que el programador define un comportamiento e inicia en un momento específico. Entonces, podemos decir que un sistema multitasking o multi tarea permite que ocurran varias actividades al mismo tiempo. En Java, el proceso que siempre se ejecuta es el llamado main que es a partir del cual se inicia prácticamente todo el comportamiento de nuestra aplicación, y en ocasiones a la aplicación le basta con este solo proceso para funcionar de manera adecuada, sin embargo, existen algunas aplicaciones que requieren más de un proceso (o hilo) ejecutándose al mismo tiempo (multithreading). Un claro ejemplo, es el sistema operativo Windows Xp,o Vista o Seven. Los tres son sistemas operatvos multi tarea, dado que podemos realizar varias tareas al mismo tiempo: abrir un documento word, navegar a internet, escucha música, bajar el material del curso de Java, etc.

4 Multitasking Process-based multitasking
Permite que los procesos (por ejemplo programas) corran concurrentemente en una computadora Thread-based multitasking Permite que partes(tareas) del mismo programa corran concurrentemente en una computadora La secuencia de código ejecutado para cada tarea define un camino(path) de ejecución independiente llamado thread(hilo) de ejecución. Existen dos tipos de multi tarea: orientado a procesos y orientado a hilos. El primero, se basa en procesos, donde varios de éstos pueden ser ejecutados de forma concurrente en una computadora. Ejemplo: abrir un documento word, navegar a internet, escucha música, bajar el material del curso de Java, etc. El segundo, se basa en hilos, por lo que son partes de un mismo programa que se ejecutan de forma concurrente. Un hilo, puede manejar otros hilos. Por ejemplo: descargar los mails y enviar los mensajes que se encuentran en bandeja de salida de forma concurrente. En este caso, un mismo hilo podría manejar ambas tareas de forma simultánea, si lo hiciera con dos hilos distintos.

5 Multitasking En un ambiente que no es multi-thread (single-threaded environment), solo una tarea se ejecuta por vez. Se desperdician ciclos de CPU por ejemplo esperando por entrada de datos del usuario Multitasking permite aprovechar esos ciclos desperdiciados. Hoy en día la mayoría de los sistemas operativos son multitarea. En un ambiente que no sea multi tarea, se ejecuta una instrucción por vez, haciendo que se desperdicien recursos, como ser la CPU.

6 Multitasking Algunas ventajas del multitasking basado en threads en comparación con el basado en procesos: Los threads comparten el mismo espacio de direcciones El cambio de contexto entre threads es habitualmente menos costoso que entre procesos El costo de la comunicación entre threads es relativamente bajo Se tienen muchas ventajas en un sistema multi-hilos sobre uno basado en procesos: El tiempo de crear un hilo nuevo en un proceso existente es menor que en crear un proceso. El tiempo necesario para terminar un hilo es menor que el de terminar un proceso, ya que cuando se elimina un proceso se debe eliminar el BCP del mismo, mientras que un hilo se elimina su contexto y pila. Se tarda mucho menos tiempo en cambiar entre dos hilos de un mismo proceso Los hilos aumentan la eficiencia de la comunicación entre programas en ejecución. En la mayoría de los sistemas en la comunicación entre procesos debe intervenir el núcleo para ofrecer protección de los recursos y realizar la comunicación misma. En cambio, entre hilos pueden comunicarse entre sí sin la invocación al núcleo. Por lo tanto, si hay una aplicación que debe implementarse como un conjunto de unidades de ejecución relacionadas, es más eficiente hacerlo con una colección de hilos que con una colección de procesos separados.

7 Multitasking Java soporta thread-based multitasking y proveee facilidades de alto nivel para la programación multihilos Thread safety es un término que se utiliza para describir el diseño de clases que aseguran que el estado de sus objetos es siempre consistente, aún cuando sean utilizados concurrentemente por múltiples threads Si bien el manejo de hilos es de gran utilidad, se debe tener cuidado en cómo estos son manejados. Por esta razon, Thread safety es un término utilizado para describir el buen diseño de clases que manejan hilos, es decir, una rutina que sea llamada por múltiples hilos sin la interacción de hilos que no se necesitan.

8 Threads (Hilos) Comparten el proceso que corre el programa
Cada thread en Java es creado y controlado por un único objeto de la clase java.lang.Thread Los threads hacen que el ambiente de ejecución sea asíncrono, permitiendo realizar distintas tareas concurrentemente Al realizar tareas de forma cuncurrente, vemos que Java nos permite manejar sistemas multitasking. Por otro lado, cuando queremos ejecutar alguno de nuestros programas, Java define un único hilo de ejecución, el cual está determinado por el método main. Dicho método, define un hilo, creado y controlado por la clase java.lang.Thread.

9 Thread Principal El entorno de ejecución distingue entre threads del usuario y “deamon” threads (demonios) Mientras un hilo del usuario esté vivo, la JVM no termina Los demonios ejecutan mientras haya threads del usuario activos, existen solo para servir a otros threads de usuario Un demonio, es un hilo que se siempre esta en ejecución. Al crear nuestras aplicaciones en Java, podemos notar que nuestro hilo main, solo puede ser parado si hacemos stop en el mismo. Por lo tanto, mientras éste se encuentre vivo, la JVM no habrá terminado.

10 Thread Principal Cuando corremos una aplicación standalone, automáticamente se crea un thread del usuario para ejecutar el método main Este thread se llama “main thread” Si no se crean otros threads a partir de este, entonces el programa finaliza cuando se termine de ejecutar el método main Como recordaremos, al ejecutar nuestros programas, tenemos el método main: public static void main(String args[]){ } Dicho main, define un único hilo de ejecución.

11 Thread Principal Si se crean otros threads a partir del main thread, estos heredan el estado de thread de usuario. En este último caso, programa no finalizará cuando se termine el main, sino cuando finalicen todos los threads de usuario Cuando creamos una aplicación, esta se ejecuta en un hilo principal al cual se le pueden agregar más hilos, de manera que éstos corran de forma paralela. Una llamada al método setDeamon(boolean) de la clase Thread determina si el thread creado va a ser tratado como daemon o como uno de usuario. Esta llamada debe hacerse luego de crear el thread y antes de ejecutarlo.

12 Creación de Threads Los threads en java son representados por un objeto de la clase Thread Se puede implementar un thread de alguna de las siguientes formas: Implementando la interfaz java.lang.Runnable Extendiendo la clase java.lang.Thread

13 Implementando la Interfaz Runnable
La interfaz tiene la siguiente especificación: public interface Runnable { void run(); } Un thread que sea creado implementando esta interfaz ejecutará el código definido en el método public run() La interfaz Runnable proporciona la capacidad de añadir la funcionalidad de un hilo a una clase simplemente implementando la interfaz, en lugar de derivándola de la clase Thread. Las clases que implementan la interfaz Runnable proporcionan un método run que es ejecutado por un objeto hilo asociado que es creado aparte. Esta es una herramienta muy útil y a menudo es la única salida que tenemos para incorporar multihilo dentro de las clases. El thread termina cuando finaliza el método run( ) normalmente o lanzando una excepción.

14 Creando Threads: Interfaz Runnable
Una clase implementa la interfaz Runnable y provee el método run() que va a ser ejecutado por el thread. Este objeto es un objeto Runnable. Se crea un objeto de la clase Thread pasándole un objeto Runnable como argumento al constructor. Se invoca al método start() sobre el objeto Thread. Este método retorna inmediatamente luego de que el nuevo Thread comienza a ejecutar. Es método run() del objeto Runnable es ejecutado (eventualmente) por el objeto Thread. Ejemplo: class MiHilo implements Runnable{ public void run(){ System.out.println("Trabajo por hacer dentro de MiHilo"); } } Para crear una instancia: MiHilo h = new MiHilo(); Thread t = new Thread(h); // Pasas tu implementación de Runnable al nuevo Thread

15 Creando Threads: Interfaz Runnable
En el diagrama anterior, vemos la secuencia de la ejecución del de un hilo que implementa la interfaz Runnable. El flujo es el siguiente: 1- se crea un objeto runnable, denominado runner. 2- se crea un hilo, al cual se le pasa por parámetro el objeto runner. 3- una vez creado el hilo, se ejecuta el método start() de dicho hilo. 4- inmediatamente, se ejecuta el método run() 5- finalmente, dos threads se encuentran en ejecución de forma concurrente.

16 Extendiendo la clase Thread
Una clase extiende Thread sobrescribiendo el método run() para definir el código a ejecutar por el Thread. Esta sublcase puede llamar al constructor de la clase Thread utilizando la llamada super() El método start() heredado de la clase Thread es invocado sobre el objeto que extiende Thread para que este se convierta en elegible para ser ejecutado. Extendiendo la clase Thread, es la segunda forma de trabajar con hilos. La clase Thread es la clase responsable de producir hilos funcionales para otras clases. Para añadir la funcionalidad de hilo a una clase simplemente se deriva la clase de Thread y se ignora el método run. Es en este método run donde el procesamiento de un hilo toma lugar, y a menudo se refieren a él como el cuerpo del hilo. La clase Thread también define los métodos start y stop, los cuales te permiten comenzar y parar la ejecución del hilo, además de un gran número de métodos útiles. Ejemplo: class MiThread extends Thread { public void run() { . . . }

17 Extendiendo la clase Thread
En este diagrama, vemos la creación de un Thread. Primero, se crea una instancia de dicha clase, para luego ejecutar el método start(). Ejemplo: class MiHilo extends Thread{ public void run(){ System.out.println("Trabajo por hacer dentro de MiHilo"); } public void run(String s){ System.out.println("La cadena ingresada es " + s); } } Para crear una instancia: MiHilo h = new MiHilo();

18 Creando Threads… Implementando Runnable:
Mejor Diseño orientado a objeto Herencia simple o individual Consistencia Extendiendo Thread: Código más sencillo El proceso de creación de hilos, son dos los mecanismos que nos permiten llevarlo a cabo en Java: implementando la interfaz Runnable, o extendiendo la clase Thread, esto es, creando una subclase de esta clase. Lo más habitual es crear hilos implementando la interfaz Runnable, dado que las interfaces representan una forma de encapsulamiento del trabajo que una clase debe realizar. Así, se utilizan para el diseño de requisitos comunes a todas las clases que se tiene previsto implementar. La interfaz define el trabajo, la funcionalidad que debe cubrirse, mientras que la clase o clases que implementan la interfaz realizan dicho trabajo (cumplen esa funcionalidad). Todas las clases o grupos de clases que implementen una cierta interfaz deberán seguir las mismas reglas de funcionamiento. Por otro lado, al extender de la clase Thread, sólo debemos completar el método run(), con las tareas que queremos que se ejecuten, por lo que el código es mas sencillo.

19 Métodos – java.lang.Thread
Dentro de dicha clase java.lang.Thread , encontramos los siguientes métodos: Thread(Runnable threadTarget) Thread(Runnable threadTarget, String threadName) static Thread currentThread() final String getName() final void setName(String name) void run() : La clase Thread implementa la interfaz Runnable dando una implementación para el método run(). Esta implementación no hace nada. Las sublcases de Thread deben sobrescribir este método. final void setDaemon(boolean flag) final boolean isDaemon() void start() : Larga un nuevo Thread. Es una llamada asíncrona (retorna inmediatamente) Thread.sleep() : Pone al thread en espera La diferencia entre ambos métodos de creación de hilos en Java radica en la flexibilidad con que cuenta el programador, que es mayor en el caso de la utilización de la interfaz Runnable. En base al ejemplo anterior, se podría extender la clase MiThread a continuación, si fuese necesario. La mayoría de las clases creadas que necesiten ejecutarse como un hilo implementarán la interfaz Runnable, ya que así queda cubierta la posibilidad de que sean extendidas por otras clases. Por otro lado, es un error pensar que la interfaz Runnable está realizando alguna tarea mientras un hilo de alguna clase que la implemente se está ejecutando. Es una interfaz, y como tal, sólo contiene funciones abstractas (concretamente una única, run), proporcionando tan solo una idea de diseño, de infraestructura, de la clase Thread, pero ninguna funcionalidad. Métodos – java.lang.Thread Thread(Runnable threadTarget) static Thread currentThread() final String getName() final void setName(String name) void run() final void setDaemon(boolean flag) final boolean isDaemon()

20 Sincronización Los Threads comparten el mismo espacio de direcciones, por ende, pueden compartir recursos. Hay situaciones críticas en las que se desea que solo un thread a la vez pueda acceder a un recurso compartido. Java provee mecanismos de sincronización para controlar el acceso a recursos compartidos Uno de los principales problemas del manejo de Threads, es el acceso a los recursos, dado que podemos tener varios hilos que intentan su acceso al mismo tiempo. Para manejar este tipo de situaciones, existen ciertas técnicas.

21 Sincronización class Counter {
private int count = 0; public void increment() { int n = count; count = n+1; } } ¿Que pasaría si dos threads comparten un objeto Counter c y ambos intentan ejecutar c.increment() ? En este ejemplo, vemos un claro escenario de acceso concurrente a un recurso. Cómo el recurso es accedido al mismo tiempo por dos hilos, el sistema o nuestro programa deberá manejar la concurrencia de acceso al mismo, y decidir quién obtiene primero el acceso. Para manejar este tipo de situaciones, existen varios mecanismos, los cuales veremos más adelante.

22 Locks Un lock (o monitor) se utiliza para sincronizar el acceso a un recurso compartido. Puede asociarse a un recurso compartido. Los Threads ganan acceso al recurso cuando son los primeros en obtener el lock asociado al mismo Los locks logran exclusión mutua Dentro de los mecanismos para sincronizar el acceso a un recurso, tenemos los locks, llamados también candados. El objetivo es obtener el bloqueo de dicho recurso. Cuando un hilo obtiene dicho recurso, lo bloquea de manera que otros hilos, no pueden tener acceso a el.

23 Locks En Java todos los objetos tienen un lock
El lock de cualquier objeto puede ser utilizado para implementar exclusión mutua. Asociando un recurso compartido con un objeto Java y su lock, el objeto actúa como guardia asegurando acceso sincronizado al recurso Solo un thread a la vez podrá acceder al recurso controlado por el objeto lock El mecanismo de locks fuerza el cumplimiento de las siguientes reglas: Un thread debe obtener el lock del objeto asociado al recurso compartido antes de poder ingresar al mismo. El runtime asegura que ningún otro thread pueda entrar al recurso compartido. Si un thread no puede adquirir el lock del objeto inmediatamente, queda bloqueado, esperando que el lock quede disponible. Cuando un thread sale de un recurso compartido, el runtime asegura que se libere el lock asociado al recurso

24 Bases para la ejecución sincronizada de código
La palabra clave synchronized y el lock forman las bases para implementar ejecución sincronizada. Existen dos variantes: Métodos synchronized Bloques synchronized Como mencionamos anteriormente, para bloquear un objeto e impedir que otro hilo lo utilice se debe sincronizar su acceso al mismo. Para ello, Java define mecanismos de sincronización y bloqueo. Entre ellos encontramos métodos synchronized y bloques synchronized.

25 Métodos synchronized Si un método de un objeto debe ser ejecutado de a un thread a la vez, entonces la declaración deberá tener la keyword synchronized Un thread que intente ejecutar este método deberá primero obtener el lock del objeto antes de poder ejecutar El lock se “solicita” invocando el método Ejemplo: public class Counter{ private int count = 0; public int inc(){ synchronized(this){ return ++count; } En este ejemplo, vemos que se utiliza un bloque sincronizado. Este bloquea el método inc, y se asegura de que sólo un hilo pueda ejecutar la línea return ++count;. Mientras que la ejecución de métodos sincronizados sobre el lock del propio objeto, los bloques sincronizados permiten la ejecución de código arbitrario sincronizado sobre el lock de cualquier otro objeto.

26 Métodos synchronized Mientras un thread está dentro de un método synchronized, todos los demás threads que intenten ejecutar este método u otros métodos synchronized del objeto deberán esperar Esta restricción no se aplica al objeto que tenga el lock Los métodos estáticos sincronizan con el lock de la clase (independiente del lock de los objetos de la clase) Veamos un ejemplo: public class Cuenta{ private double monto = 0; public double depositar(double d){ synchronized(this){ return monto+=d; } }} En este ejemplo, vemos que la clase Cuenta posee un atributo de tipo double, denominada monto. Al utilizar synchronized, la modificación de dicha variable se realizara de forma sincronizada. De esta manera, si otro thread intenta modificar dicha variable, ésta deberá esperar hasta obtener el bloqueo de la misma.

27 Bloques synchronized public Object pop() { synchronized (this) {
// Synchronized block on current // object // ... } También puede especificarse el lock de una clase: synchronized (<class name>.class) { <code block> } En este caso, vemos la estructura del bloque synchronized.

28 Synchronized Los siguientes dos segmentos de código son equivalentes:
public void push(char c) { synchronized(this) { : } public synchronized void push(char c) {

29 Sincronización Donde:
class Counter { private int count = 0; public void synchronized Increment() { int n = count; count = n+1; } } En este ejemplo, vemos cómo dos Threads quieren acceder al mismo tiempo a la variable count. Sin embargo, al utilizar los bloques synchronized, el thread 2 debe esperar a obtener el bloqueo de dicha variable. Una vez que el Thread 1, libera la variable, es cuando el Thread 2, puede comenzar a acceder a ella.

30 Transiciones Durante el ciclo de vida de un thread, éste se puede encontrar en diferentes estados. Entre ellos, tenemos los que se describen en la figura anterior.

31 Control Básico de los Threads
Testeando threads: isAlive() – determina si el hilo está vivo. Accediendo a thread priority: getPriority() setPriority() Poniendo threads en espera: Thread.sleep() join() Thread.yield() Ejemplos: HiloRunnable h = new HiloRunnable(); Thread t = new Thread(h); t.setPriority(7); //seteamos prioridad a 7 t.start(); //iniciamos el thread If (t.isAlive()) System.out.print(“Is alive!!”); try{ Thread.sleep(5*60*1000); //Duerme durante 5 minutos }catch(InterruptedException ex){} El método yield(): El método yield() tiene la función de hacer que un hilo que se está ejecutando de regreso al estado en ejecución(runnable) para permitir que otros hilos de la misma prioridad puedan ejecutarse. Sin embargo, el funcionamiento de este método (al igual que de los hilos en general) no está garantizado, puede que después de que se establezca un hilo por medio del método yield() a su estado en ejecución(runnable), éste vuelva a ser elegido para ejecutarse. El método yield() nunca causará que un hilo pase a estado de espera/bloqueado/dormido, simplemente pasa de ejecutándose(running) a en ejecución(runnable). El método join(): El método no estático join() permite al hilo "formarse en la cola de espera" de otro hilo. Si tienes un hilo B que no puede comenzar a ejecutarse hasta que se complete el proceso del hilo A, entonces querrás que B se forme en la cola de espera de A. Esto significa que B nunca podrá ejecutarse si A no completa su proceso. En código se utiliza así:

32 Método Join public static void main(String[] args) {
Thread t = new Thread(new Runner()); t.start(); ... // Do stuff in parallel with the other thread for a while // Wait here for the timer thread to finish try { t.join(); } catch (InterruptedException e) { // t came back early } // Now continue in this thread En este ejemplo, creamos un Thread, lo inciamos, se ejecuta en paralelo alguna tarea, y luego, éste espera a que el otro thread termine, mediante el método join.

33 Otras formas de crear Threads
public class MyThread extends Thread { public void run() { while (running) { // do lots of interesting stuff try { sleep(100); } catch (InterruptedException e) { // sleep interrupted } public static void main(String args[]) { Thread t = new MyThread(); t.start(); En este ejemplo, mientras que el Thread se encuentra en ejecución, se realizan las tareas en paralelo y luego el thread es parado por 100 segundos, mediante el método sleep.

34 Diagrama de estado de threads
El siguiente diagrama muestra los distintos estados que puede tener un Thread Java en cualquier momento de su vida, junto con las llamadas a métodos que provocan las transiciones de un estado a otro.

35 Deadlock Es cuando dos threads esperan cada uno por un lock de parte del otro No es detectado o evitado Puede ser evitado mediante: Decidir el orden para obtener locks Adherirse a este orden durante todo el proceso Liberar los locks en el orden inverso Un conjunto de procesos se encuentran en deadlock, cuando cada uno de ellos está bloqueado esperando por un evento asociado a otro proceso del conjunto. En esta situación, no hay forma que los procesos en deadlock por sí mismos salgan de dicho estado. De esta manera, si se encuentran en deadlock, es porque están bloqueados.

36 Interacción de thread: wait y notify
Escenario: Considere que ud. y el chofer de un taxi son dos threads El problema: Cómo determinar cuándo está ud. en su destino? La solución: Le comunica al chofer sobre su destino y se relaja El taxista conduce y lo notifica cuando arriba a su destino

37 Interacción de thread Los métodos utilizados son wait y notify
Existen dos pooles: wait lock wait() y notify() funcionan como una lista de espera. Si varios hilos van llamando a wait() quedan bloqueados y en una lista de espera, de forma que el primero que llamó a wait() es el primero de la lista y el último es el útlimo. Cada llamada a notify() despierta al primer hilo en la lista de espera, pero no al resto, que siguen dormidos. Necesitamos por tanto hacer tantos notify() como hilos hayan hecho wait() para ir despertándolos a todos de uno en uno. Si hacemos varios notify() antes de que haya hilos en espera, quedan marcados todos esos notify(), de forma que los siguientes hilos que hagan wait() no se quedaran bloqueados. En resumen, wait() y notify() funcionan como un contador. Cada wait() mira el contador y si es cero o menos se queda bloqueado. Cuando se desbloquea decrementa el contador. Cada notify() incrementa el contador y si se hace 0 o positivo, despierta al primer hilo de la cola.

38 Diagrama de estado de threads con wait y notify
En esta figura, vemos los estados posibles de un thread con los métodos wait y notify.

39 Modelo monitor para sincronización
Deja los datos compartidos en un estado consistente Asegura que los programas no pueden estancarse No coloca threads que esperan diferentes notificaciones en el mismo pool de wait El lenguaje Java y el sistema de ejecución soportan la sincronizaxión de threads mediante el uso de monitores. En general, un monitor está asociado con un objeto especifico (una condición variable) y funciona como un bloqueo para ese dato. Cuando un thread mantiene el monitor para algún dato del objeto, los otros threads están bloqueados y no pueden ni leer ni modificar el dato. Los segmentos de código dentro de programa que acceden al mismo dato dentro de threads concurrentes separados son conocidos como secciones críticas. En el lenguaje Java, se pueden marcar las secciones críticas del programa con la palabra clave synchronized.

40 Productor public void run() { char c;
for (int i = 0; i < 200; i++) { c = (char)(Math.random() * 26 +'A'); theStack.push(c); System.out.println("Producer" + num + ": " + c); try { Thread.sleep((int)(Math.random() * 300)); } catch (InterruptedException e) { // ignore it } Java utiliza la idea de monitores para sincronizar el acceso a datos. Un monitor es un lugar bajo guardia donde todos los recursos tienen el mismo candado. Sólo una llave abre todos los candados dentro de un monitor, y un hilo debe obtener la llave para entrar al monitor y acceder a esos recursos protegidos. Si varios hilos desean entrar al monitor simultáneamente, sólo uno obtiene la llave. Los otros deben esperar fuera hasta que el hilo con la llave termine de usar el recurso exclusivo y devuelva la llave a la Máquina Virtual Java. Puede ocurrir deadlock si dos hilos están esperando por el recurso cuya llave la tiene el otro. En un momento un hilo puede tener las llaves de varios monitores. En Java los recursos protegidos por un monitor son fragmento de programa en forma de métodos o bloques de sentencias encerradas con paréntesis {}. Un clásico ejemplo, es el del productor/consumidor. Imaginemos que tenemos un productor que produce números de forma constante y a medida que va produciendo, coloca lo producido en un stack.

41 Consumidor public void run() { char c;
for (int i = 0; i < 200; i++) { c = theStack.pop(); System.out.println("Consumer" + num + ": " + c); try { Thread.sleep((int)(Math.random() * 300)); } catch (InterruptedException e) { } } Ahora bien… por otro lado, tenemos el consumidor, quien consume los productos generados por el productor del mismo stack.Pero, que pasaría si en determinado momento, el productor al mismo momento en que va a colocar un elemento en el stack, el consumidor también intenta acceder a ese elemento? Para solucionar este problema, se utilizan los monitores.

42 La clase syncstack public class SyncStack {
private List buffer = new ArrayList(400); public synchronized char pop() { } public synchronized void push(char c) { En este caso, vemos que la clase SynStack, maneja los métodos pop y push de forma sincronizada a traves del uso de synchronized. Pop, se utiliza para obtener el último elemento de la pila, mientras que push, coloca un elemento en el tope de esta.

43 El método pop public synchronized char pop() { char c;
while (buffer.size() == 0) { try { this.wait(); } catch (InterruptedException e) { // ignore it... } c = ((Character)buffer.remove(buffer.size()-1)).charValue(); return c; En este código, vemos que mientras que la pila tenga cero elementos, el thread esperara. Cuando esta condición no se cumpla, es decir, que hayan elementos, entonces se sacará el último elemento de la lista.

44 El método push public synchronized void push(char c) { this.notify();
Character charObj = new Character(c); buffer.addElement(charObj); } El método push, solamente agrega los caracteres producidos a la lista de forma synchronized.

45 Synctest.Java A continuación veremos un ejemplo clásico: Productor/Consumidor. Tenemos 2 productores y 2 consumidores, los cuales utilizan un mismo recurso. En este ejemplo se, maneja la concurrencia al recurso compartido, el cual es producido por prodT1 y prodT2 y consumido por c1 y c2. Para ejecutar este ejemplo, se crearon productores y consumidores, con sus respectivos hilos, de manera de poder probar la sincronización de acceso a los elementos de la pila. package mod13; public class SyncTest { public static void main(String[] args) { SyncStack stack = new SyncStack(); Producer p1 = new Producer(stack); Thread prodT1 = new Thread (p1); prodT1.start(); Producer p2 = new Producer(stack); Thread prodT2 = new Thread (p2); prodT2.start(); Consumer c1 = new Consumer(stack); Thread consT1 = new Thread (c1); consT1.start(); Consumer c2 = new Consumer(stack); Thread consT2 = new Thread (c2); consT2.start();} }

46 Producer.Java La clase Productor, implementa la interfaz Runnable. En su método run, produce todos los elementos y los coloca en la pila. Entre que coloca un elemento y otro, se ejecuta el método sleep, de manera de dormir el thread por unos instantes, de manera que otros productores tengan acceso a la pila para colocar sus elemento producidos, o bien un consumidor consuma de la misma. package mod13; public class Producer implements Runnable { private SyncStack theStack; private int num; private static int counter = 1; public Producer (SyncStack s) { theStack = s; num = counter++;} public void run() { char c; for (int i = 0; i < 200; i++) { c = (char)(Math.random() * 26 +'A'); theStack.push(c); System.out.println("Producer" + num + ": " + c); try { Thread.sleep((int)(Math.random() * 300)); } catch (InterruptedException e) { // ignore it }}} }

47 Consumer.Java Ahora veremos el código correspondiente a un Consumidor.
Análogamente, el Consumidor también implementa la interfaz runnable. El método run, es muy similar al de la clase Productor, solo que en este caso, se consume de la pila. package mod13; public class Consumer implements Runnable { private SyncStack theStack; private int num; private static int counter = 1; public Consumer (SyncStack s) { theStack = s; num = counter++; } public void run() { char c; for (int i = 0; i < 200; i++) { c = theStack.pop(); System.out.println("Consumer" + num + ": " + c); try { Thread.sleep((int)(Math.random() * 300)); } catch (InterruptedException e) { } }} }

48 Syncstack.Java La clase syncstack, es quien maneja la concurrencia al stack, mediante los métodos push y pop, ya que hace uso de los métodos wait() y notify(), de manera que mediante el wait, el thread espera para obtener el bloqueo y el notify, le avisa que ya termino y el recurso esta libre. A continuación veremos un ejemplo. package mod13; import java.util.*; public class SyncStack { private List buffer = new ArrayList(400); public synchronized char pop() { char c; while (buffer.size() == 0) { try { this.wait(); } catch (InterruptedException e) { // ignore it... } c = ((Character)buffer.remove(buffer.size()-1)). charValue(); return c;} public synchronized void push(char c) { This.notify(); Character charObj = new Character(c); buffer.add(charObj); } }

49 Syncstackexample Producer2: F Consumer1: F Producer2: K Consumer2: K
Producer2: T Producer1: N Producer1: V Consumer2: V Consumer1: N Producer2: V Producer2: U Consumer2: U Producer1: F Producer2: M Consumer2: M Consumer2: T La salida de la ejecución del programa, es la que se muestra anteriormente.

50 RESUMEN Multitareas Hilos – Procesos Locks Monitores Deadlocks
Ejemplos: productor y consumidor

51 Bibliografía The Java Tutorial Trail de Threads


Descargar ppt "CJ02 – Técnicas avanzadas en java 1.4 y 5.0"

Presentaciones similares


Anuncios Google