Post on 03-Jan-2015
Multitareas Hilos – Procesos Locks Monitores Deadlocks Ejemplos: productor y consumidor
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)
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.
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.
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
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
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
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
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
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
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
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()
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.
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.
Implementando Runnable:₋ Mejor Diseño orientado a objeto₋ Herencia simple o individual ₋ Consistencia
Extendiendo Thread:₋ Código más sencillo
Thread(Runnable threadTarget)
static Thread currentThread()
final String getName()
final void setName(String name)
void run()
final void setDaemon(boolean flag)
final boolean isDaemon()
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
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() ?
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
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
La palabra clave synchronized y el lock forman las bases para implementar ejecución sincronizada.
Existen dos variantes:₋ Métodos synchronized₋ Bloques 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
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)
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> }
Los siguientes dos segmentos de código son equivalentes:
public void push(char c) {synchronized(this) {:}
}
public synchronized void push(char c) {:
}
Testeando threads:₋ isAlive() – determina si el hilo está vivo.
Accediendo a thread priority:₋ getPriority()₋ setPriority()
Poniendo threads en espera:₋ Thread.sleep()₋ join()₋ Thread.yield()
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
... }
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(); } }
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
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
Los métodos utilizados son wait y notify
Existen dos pooles:₋ wait₋ lock
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
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
} } }
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) { }
} }
public class SyncStack {private List buffer = new ArrayList(400);public synchronized char pop() {}public synchronized void push(char c) {}
}
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.addElement(charObj);
}
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.
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.
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.
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.
Producer2: FConsumer1: FProducer2: KConsumer2: KProducer2: TProducer1: NProducer1: VConsumer2: VConsumer1: NProducer2: VProducer2: UConsumer2: UConsumer2: VProducer1: FConsumer1: FProducer2: MConsumer2: MConsumer2: T
Multitareas Hilos – Procesos Locks Monitores Deadlocks Ejemplos: productor y consumidor
The Java TutorialTrail de Threadshttp://java.sun.com/docs/books/tutorial/index.html