21
Java: Programación Multithread Franco Guidi Polanco Escuela de Ingeniería Industrial Pontificia Universidad Católica de Valparaíso, Chile [email protected] 09-03-2007 Franco Guidi Polanco 2 ¿Qué es un thread? Hasta el momento hemos desarrollado programas secuenciales con un único thread: en cualquier instante durante la ejecución de un programa hay un único punto de ejecución. Un programa Un thread 09-03-2007 Franco Guidi Polanco 3 ¿Qué es un thread? (cont.) Un thread es un flujo secuencial de control dentro de un programa Un programa puede tener más de un thread ejecutándose al mismo tiempo Un programa Dos threads 09-03-2007 Franco Guidi Polanco 4 Ejemplo Una aplicación que posee un thread que saluda y otro que se despide: for(int i = 1; i<100;i++) System.out.printl(“Hola ” + i ); Un programa Dos threads for(int i = 1; i<100;i++) System.out.printl(“Chao ” + i );

Franco Guidi Polancoeii.ucv.cl/pers/guidi/cursos/estructuras/pdf/Java-Programacion... · 3/9/2007  · Java: Extendiendo la clase java.lang.Thread Implementando la interfaz java.lang.Runnable

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Java: Programación Multithread

Franco Guidi PolancoEscuela de Ingeniería Industrial

Pontificia Universidad Católica de Valparaíso, [email protected]

09-03-2007Franco Guidi Polanco 2

¿Qué es un thread?

Hasta el momento hemos desarrollado programas secuenciales con un único thread: en cualquier instante durante la ejecución de un programa hay un único punto de ejecución.

Un programaUn thread

09-03-2007Franco Guidi Polanco 3

¿Qué es un thread? (cont.)

Un thread es un flujo secuencial de control dentro de un programaUn programa puede tener más de un threadejecutándose al mismo tiempo

Un programa

Dos threads

09-03-2007Franco Guidi Polanco 4

Ejemplo

Una aplicación que posee un thread que saluda y otro que se despide:

for(int i = 1; i<100;i++)

System.out.printl(“Hola ” + i );

Un programa

Dos threads

for(int i = 1; i<100;i++)

System.out.printl(“Chao ” + i );

09-03-2007Franco Guidi Polanco 5

Ejemplo (cont.)

Output posible:

Hola 1Hola 2Chao 1Hola 3Chao 2Chao 3Hola 4…

09-03-2007Franco Guidi Polanco 6

Creación de múltiples threads en Java

Los threads en Java se implementan por medio de la clase java.lang.Thread.

Existen dos formas de crear threads en Java:

Extendiendo la clase java.lang.ThreadImplementando la interfaz java.lang.Runnable

09-03-2007Franco Guidi Polanco 7

Creación de threads extendiendo la clase java.lang.Thread

El thread debe ser una clase que extiende la clase ThreadSe debe sobreescribir el método run() con el código que deberá ser ejecutado por el thread.El thread debe ser lanzado invocando el método start() del objeto (heredado de la clase Thread)

09-03-2007Franco Guidi Polanco 8

Creación de threads extendiendo la clase java.lang.Thread: ejemplo 1

Dos ejemplos de clases que extienden Thread:

public class Chao extends Thread {public void run() {

for (int i=1;i<100;i++)System.out.println( "Chao" + i );

}}

public class Hola extends Thread {public void run() {

for (int i=1;i<100;i++)System.out.println( "Hola" + i );

}}

09-03-2007Franco Guidi Polanco 9

Creación de threads extendiendo la clase java.lang.Thread: ejemplo 1 (cont.)

La aplicación que lanza los threads:

Notar que para lanzar un nuevo thread se debe invocar el método start(). La simple invocación del método run()produce sólo su ejecución en el thread actual (como cualquier otro método).

public class EjemploThreadSimple {public static void main(String arg[]) {

Hola h = new Hola();Chao c = new Chao();h.start();c.start();System.out.println( “Fin programa” );

}}

09-03-2007Franco Guidi Polanco 10

¿Qué ocurre en este ejemplo?

...public static void main(String arg[]) {

Hola h = new Hola();

Chao c = new Chao();

h.start();

c.start();

System.out.println( “Fin programa” );

}...

Threadmain

...h.start();

c.start();

...

ThreadHola

ThreadChao

09-03-2007Franco Guidi Polanco 11

Ejecución del ejemplo 1

NOTA:Ejecución en JVM sobre Windows XP.El resultado puede ser distinto en otras plataformas(se estudiará más adelante)

09-03-2007Franco Guidi Polanco 12

Si las referencias a los threads no son necesarias...

Estos dos programas funcionan de igual modo

public class EjemploThreadSimple {public static void main(String arg[]) {

Hola h = new Hola();Chao c = new Chao();h.start();c.start();System.out.println( “Fin programa” );

}}

public class EjemploThreadSimple {public static void main(String arg[]) {

new Hola().start();new Chao().start();System.out.println( “Fin programa” );

}}

09-03-2007Franco Guidi Polanco 13

Creación de threads implementando la interfaz java.lang.Runnable

La clase que contiene el proceso que será lanzado dentro de un nuevo thread debe implementar la interfaz java.lang.RunnableParticularmente se debe implementar en la clase el método run() declarado en Runnable, con el código que deberá ser ejecutado por el threadPara lanzar el thread se debe crear una instancia de java.lang.Thread, pasando al constructor de éste una referencia al objeto que implementa Runnable, y luego se debe invocar el método start() del objeto Thread

09-03-2007Franco Guidi Polanco 14

Creación de threads implementando la interfaz java.lang.Runnable (cont.)

Clase que implementa el código del thread:public class MiThread implements Runnable {

...public void run() {

// código del thread}...

}

...Thread t = new Thread( new MiThread() );t.start();...

Instanciación y lanzamiento del thread:

09-03-2007Franco Guidi Polanco 15

Creación de threads implementando la interfaz java.lang.Runnable: ejemplo 2

Dos ejemplos de clases que implementan Runnable:

public class Chao implements Runnable {public void run() {

for (int i=1;i<100;i++)System.out.println( "Chao" + i );

}}

public class Hola implements Runnable {public void run() {

for (int i=1;i<100;i++)System.out.println( "Hola" + i );

}}

09-03-2007Franco Guidi Polanco 16

Creación de threads implementando la interfaz java.lang.Runnable: ejemplo 2 (cont.)

La aplicación que lanza los threads:

Notar que para lanzar un nuevo thread se debe crear una instancia de Thread pasando al constructor una referencia a un objeto Runnable, e invocar el método start() sobre Thread. La simple invocación del método run() produce sólo su ejecución en el thread actual (como cualquier otro método).

public class EjemploThreadSimple {public static void main(String arg[]) {

Thread h = new Thread( new Hola() );Thread c = new Thread( new Chao() );h.start();c.start();System.out.println( “Fin programa” );

}}

09-03-2007Franco Guidi Polanco 17

(Nuevamente) si las referencias a los threads no son necesarias...

Estos dos programas funcionan de igual modo

public class EjemploThreadSimple {public static void main(String arg[]) {

Thread h = new Thread( new Hola() );Thread c = new Thread( new Chao() );h.start();c.start();System.out.println( “Fin programa” );

}}

public class EjemploThreadSimple {public static void main(String arg[]) {

new Thread( new Hola() ).start();new Thread( new Chao() ).start();System.out.println( “Fin programa” );

}}

09-03-2007Franco Guidi Polanco 18

Ejercicio

Cree un programa similar al ejemplo anterior que, en vez de usar clases distintas para manejar los threads correspondientes a los diferentes mensajes (“Hola, “Chao”), utilice dos thread de una misma clase (cuyo mensaje sea configurable). Haga una implementación del thread extendiendo la clase Thread y otra implementando Runnable.Haga la aplicación que lance los threads.

09-03-2007Franco Guidi Polanco 19

public class Ejercicio {public static void main(String arg[]) {

Habla h = new Habla( “Hola” ) ;Habla c = new Habla( “Chao” );h.start();c.start();

}}

Versión que extiende Thread:

Solución

public class Habla extends Thread {String mensaje;public Habla(String msg){

mensaje = msg;}public void run() {

for (int i=1;i<100;i++)System.out.println( mensaje + i );

}}

09-03-2007Franco Guidi Polanco 20

Solución (cont.)

Versión que implementa Runnable:

public class Ejercicio {public static void main(String arg[]) {

Thread h = new Thread( new Habla( “Hola” ) ) ;Thread c = new Thread( new Habla( “Chao” ) );h.start();c.start();

}}

public class Habla implements Runnable {String mensaje;public Habla(String msg){

mensaje = msg;}public void run() {

for (int i=1;i<100;i++)System.out.println( mensaje + i );

}}

09-03-2007Franco Guidi Polanco 21

Métodos útiles de la clase Thread: sleep

Detiene la ejecución del thread actual por la cantidad de milisegundos indicada como parámetro.

Ejemplo:public static void sleep(long millis) throws InterruptedException

public class MyThread extends Thread {...public void run() {

...try{

sleep( 1000 );}catch(InterruptedException e){

//Interrupción}...

}}

Thread.sleep( 1000 );NOTA: Si en vez de extender Thread se hubiese implementado Runnable, entonces aquí hubiese sido necesaria la referencia a la clase Thread para invocar el método sleep().

Equivalente:

09-03-2007Franco Guidi Polanco 22

Métodos útiles de la clase Thread: sleepEjemplo

public class Hola extends Thread {public void run() {

for (int i=1;i<10;i++){System.out.println( "Hola" + i );try{

Thread.sleep( 1000 );}catch(InterruptedException e){}

}}

}

Detención de 1000 ms(1 segundo)

public class Chao extends Thread {public void run() {

for (int i=1;i<10;i++){System.out.println( "Chao" + i );try{

sleep( (long) ( Math.random() * 1000) );}catch (InterruptedException e){}

}}

}

Tiempo de detención al azar

09-03-2007Franco Guidi Polanco 23

Métodos útiles de la clase Thread: sleepEjemplo

09-03-2007Franco Guidi Polanco 24

Métodos útiles de la clase Thread: yield

Detiene la ejecución del thread actual y permite a otros threads ser ejecutados

Ejemplo:public static void yield()

public class MyThread extends Thread {...public void run() {

...yield();...

}}

Thread.yield();Equivalente:

NOTA: Si en vez de extender Thread se hubiese implementado Runnable, entonces aquí hubiese sido necesaria la referencia a la clase Thread para invocar el método yield().

09-03-2007Franco Guidi Polanco 25

Métodos útiles de la clase Thread: yield(Ejemplo)

public class Hola extends Thread {public void run() {

for (int i=1;i<10;i++){System.out.println( "Hola" + i );Thread.yield();

}}

}

public class Chao extends Thread {public void run() {

for (int i=1;i<10;i++){System.out.println( "Chao" + i );yield();

}}

}

09-03-2007Franco Guidi Polanco 26

Métodos útiles de la clase Thread: yield(Ejemplo)

09-03-2007Franco Guidi Polanco 27

Ciclo de vida de un thread

Ejecutable(Runnable)

Nuevo thread(New Thread)

No ejecutable(Not Runnable)

Muerto(Dead)

Ejecutándose(Running)

start

finaliza el método run

Tomado de “The Java Tutorial”Sun Microsystems

yieldsleepwaitbloqueado en I/O

09-03-2007Franco Guidi Polanco 28

Ciclo de vida de un thread (cont.)

Inicio de un thread - el thread se ha creado (“Nuevo thread”) y se invoca el método start. El thread pasa al estado “ejecutable”.Transición del estado “ejecutable” al estado “no ejecutable”- ocurre por uno de estos tres motivos:

El thread invoca el método sleepEl thread invoca el método wait (*)El thread se bloquea en una operación de I/O

(*) será estudiado más adelante

09-03-2007Franco Guidi Polanco 29

Ciclo de vida de un thread (cont.)

Transición del estado “no ejecutable” a “ejecutable”:

si el thread había invocado el método sleep, el número de milisegundos de pausa ha transcurridosi el thread había invocado el método wait de un objeto, otro thread lo notifica de continuar por llamando al método notify o al método notifyAll del mismo objeto (*)si el thread estaba bloqueado en operación de I/O, la operación se ha completado

(*) serán estudiados más adelante

09-03-2007Franco Guidi Polanco 30

Ciclo de vida de un thread (cont.)

Transición del estado “ejecutable” al estado “muerto”: ocurre cuando el método run llega a su fin.

El método isAlive ayuda a conocer el estado de un thread:

Retorna true si el thread ha sido lanzado (método start invocado) y no detenidoRetorna false si el thread está en el estado “Nuevo thread” (no ha sido lanzado) o está “muerto” (método run terminado)

09-03-2007Franco Guidi Polanco 31

Ejecución de threads

ParalelismoEn un sistema con múltiples CPU, cada CPU podría ejecutar un thread distinto

ConcurrenciaSi no es posible el paralelismo, una CPU es responsable de ejecutar múltiples threads

CPUCPU CPU

Thread AThread BThread A

...

Thread AThread AThread A

...

Thread BThread BThread B

...

09-03-2007Franco Guidi Polanco 32

Prioridades de los threads

La ejecución de múltiples threads en una sola CPU requiere la determinación de una secuencia de ejecución (“scheduling”)Java soporta un algoritmo de secuenciación de threads simple denominado “fixed priority scheduling”Este algoritmo secuencia la ejecución de threads en base a la “prioridad relativa” que les ha sido asignada

09-03-2007Franco Guidi Polanco 33

Prioridades de los threads (cont.)

Cuando se crea un nuevo thread, su prioridad relativa es la misma que la del thread que lo creóLa prioridad de un thread puede ser cambiada en cualquier momento por medio del método setPriority. Este método recibe un entero que indica el valor de prioridad (valores más altos indican más altas prioridades)La clase Thread declara tres constantes:

public static final int MAX_PRIORITY 10public static final int MIN_PRIORITY 1public static final int NORM_PRIORITY 5

09-03-2007Franco Guidi Polanco 34

El “fixed priority scheduling” de Java

Entre todos los threads en estado “ejecutable” es escogido el thread con la prioridad más altaSi hay dos threads con la misma prioridad, es escogido uno de ellos en modo round-robinCuando el thread en ejecución pasa al estado “no ejecutable” o “muerto” otro thread es seleccionado para su ejecución. La ejecución de un thread es interrumpida si otro thread con más alta prioridad se vuelve “ejecutable”.

09-03-2007Franco Guidi Polanco 35

Threads y la portabilidad de Java: debilidades

La responsabilidad de ejecución de los threads es pasada al sistema operativoPero...

... distintos sistemas operativos manejan los threads en distinta forma: por ejemplo NT y Solaris tienen diferentes niveles de prioridades (incluso diferentes respecto los que define Java)

09-03-2007Franco Guidi Polanco 36

Threads y la portabilidad de Java: debilidades (cont.)

La cosa puede ser peor aun:existen sistemas operativos que implementan “time slicing” (subdivisión de tiempo): el sistema operativo asigna una porción de tiempo a la ejecución de cada thread. En este caso la ejecución de un thread es interrumpida no sólo si otro thread con más alta prioridad se vuelve “ejecutable”, sino también cuando su tiempo asignado de ejecución se acaba.(no todos los sistemas operativos implementan time slicing)

09-03-2007Franco Guidi Polanco 37

Threads egoístas

Si un sistema operativo no implementa time slicing, y si el thread no sale del estado “ejecutable”, este continuará su ejecución hasta que muera. Mientras tanto, ningún otro thread podrá ser ejecutado.Volvamos el ejemplo inicial:

En un sistema sin time slicing, este se vuelve un thread egoísta.

public class Habla extends Thread {String mensaje;public Habla(String msg){

mensaje = msg;}public void run() {

for (int i=1;i<100;i++)System.out.println( mensaje + i );

}}

09-03-2007Franco Guidi Polanco 38

Threads egoístas (cont.)

Por lo tanto el siguiente programa ejecutará:ambos threads contemporáneamente en un sistema con time slicing (véase su ejecución en ejemplos anteriores)sólo el thread que imprime Hola hasta terminar las 99 impresiones, y luego el thread que imprime Chao, en un sistema que no soporta time slicing

public class Ejercicio {public static void main(String arg[]) {

Habla h = new Habla( “Hola” ) ;Habla c = new Habla( “Chao” );h.start();c.start();

}}

09-03-2007Franco Guidi Polanco 39

Moraleja sobre el egoísmo y los threads

No se debe asumir que la ejecución de una aplicación se hará siempre en un sistema que soporta time slicing.Por lo tanto, se debe incluir adecuadamente invocaciones a los métodos yield, sleep y wait, si los threads no se bloquean en operaciones de I/O.public class Habla extends Thread {

String mensaje;public Habla(String msg){

mensaje = msg;}public void run() {

for (int i=1;i<100;i++){System.out.println( mensaje + i );yield();

}}

}

09-03-2007Franco Guidi Polanco 40

Acceso a datos compartidos

Es común que dos o más threads tengan acceso a objetos en comúnEjemplo

Supongamos una aplicación con dos threads que actualizan un objeto compartido, de la clase Historial:

public class Historial {String[] mensajes = new String[1000];int pos = 0;public void agregar(String msg) {

mensaje[pos] = msg;pos++;

}}

09-03-2007Franco Guidi Polanco 41

Acceso a datos compartidos (cont.)

public class Ejercicio {public static void main(String arg[]) {

Historial historial = new Historial();Habla h = new Habla( “Hola”, historial ) ;Habla c = new Habla( “Chao”, historial );h.start();c.start();

}}

public class Habla extends Thread {String mensaje;Historial historial;public Habla(String msg, Historial h){

mensaje = msg;historial = h;

}public void run() {

for (int i=1;i<100;i++){historial.agregar( mensaje );yield();

}}

09-03-2007Franco Guidi Polanco 42

Acceso a datos compartidos (cont.)

Se espera que ocurra lo siguiente:

Thread Hola Thread Chao

mensaje[pos]=msg;pos++;

mensaje[0]=“Hola”pos = 1

mensaje[1]=“Chao”pos = 2

mensaje[pos]=msg;pos++;

mensaje[2]=“Hola”pos = 3

mensaje[3]=“Chao”pos = 4

mensaje[pos]=msg;pos++;

mensaje[pos]=msg;pos++;

09-03-2007Franco Guidi Polanco 43

Acceso a datos compartidos (cont.)

Pero podría ocurrir lo siguiente:

Thread Hola Thread Chao

mensaje[pos]=msg; mensaje[0]=“Hola”mensaje[0]=“Chao”pos = 1

mensaje[pos]=msg;pos++;

mensaje[2]=“Hola”pos = 3

mensaje[3]=“Chao”

mensaje[pos]=msg;pos++;

mensaje[pos]=msg;

pos++; pos = 2

pos++; pos = 4

mensaje[3]=“Hola”mensaje[pos]=msg;

pos++; pos = 5

09-03-2007Franco Guidi Polanco 44

Bloqueo del objetos compartidos

La sincronización para el acceso a objetos compartidos se basa en el concepto de “monitor”, desarrollado por C.A.R. Hoare.Un monitor es una porción de código protegida por un “mutex” (“mutual exclusion semaphore”).Sólo un thread puede tener el mutex de un objeto en un momento dado.Si un segundo thread trata de obtener un mutex ya adquirido por otro thread, se bloquea hasta que el primero libere el mutex.Al momento de liberarse un mutex, todos los threads en espera de él se “despertarán”; en base a algún criterio (orden de prioridad, FIFO, etc.) el mutex será dado a uno de ellos.

09-03-2007Franco Guidi Polanco 45

Bloqueo del objetos compartidos: analogía

Un edificio en el cual algunas oficinas tienen llave y otras tiene libre acceso. El monitor es el conjunto de oficinas cuyo acceso requiere la llave.Los threads son las personas que quieren acceder a las oficinas.Para entrar a una oficina con llave, una persona tiene que obtener el manojo con las llaves de la oficina. El manojo con las llaves es el mutex del edificio: solo la persona que tiene el manojo puede ingresar a las oficinas con llave. Las otras son de libre acceso.

Threads

Mutex

Objeto

09-03-2007Franco Guidi Polanco 46

Bloqueo del objetos compartidos: métodos synchronized

En Java el bloqueo de un objeto ocurre cuando un threadentra a un método declarado como synchronized (de un objeto compartido).Ejemplo:

Al momento de entrar a un método synchronized de un objeto compartido, un thread se encontrará con una de las siguientes situaciones:

Mutex libre: el thread tomará el mutex, ejecutará el método y lo liberará sólo al momento de terminar la ejecución de dicho método. Mutex tomado por otro thread: el thread se bloqueará en espera de que el primero lo libere.

public class Historial {...public synchronized void agregar(String msg) {

mensaje[pos] = msg;pos++;

}}

09-03-2007Franco Guidi Polanco 47

Bloqueo del objetos compartidos: métodos synchronized (cont.)

Consecuencia: sólo un thread a la vez podrá ejecutar un método synchronized sobre un objeto. Al interrumpirse la ejecución de un thread que accede a un método synchronized, el paso se dará a otro thread que no requiera el mutex sobre tal objeto (i.e. que no esté solicitando la ejecución de cualquiera de sus métodos synchronized).Una vez liberado el mutex, los threads bloquados en espera de él se vuelven “ejecutables”.

Objeto

09-03-2007Franco Guidi Polanco 48

Bloqueo del objetos compartidos: métodos synchronized (cont.)

En el caso de nuestro ejemplo, mientras un threadse encuentra ejecutando el método agregar, ningún otro thread puede ejecutar dicho método (pues la ejecución de agregar requiere el mutexdel objeto historial).

ThreadHola

ThreadChao

historial.agregar() historial.agregar()

mensaje[pos]=msg;pos++;

mutex

historial

09-03-2007Franco Guidi Polanco 49

Bloqueo del objetos compartidos: métodos synchronized (cont.)

La invocación a sleep dentro de un thread no libera el mutex de los objetos que eventualmente pudiera tener en su poder.

Objeto

Z z zz

09-03-2007Franco Guidi Polanco 50

Métodos synchronized: ejemplo

En el ejemplo de la clase Historial, el método agregar requiere ser synchronized, en cambio getCapacidad no lo requiere:

public class Historial {String[] mensajes = new String[1000];int pos = 0;public synchronized void agregar(String msg) {

mensaje[pos] = msg;pos++;

}public int getCapacidad(){

return mensajes.length;}

}

09-03-2007Franco Guidi Polanco 51

Métodos synchronizedEn la clase Historial, ¿el método que retorna el número de elementos ingresados al arreglo debe ser synchronized?

public class Historial {String[] mensajes = new String[1000];int pos = 0;public synchronized void agregar(String msg) {

mensaje[pos] = msg;pos++;

}public int getCapacidad(){

return mensajes.length;}public synchronized int getElementos() {

return pos;}

}

¿?

09-03-2007Franco Guidi Polanco 52

¿Cuándo deben declararse métodos synchronized?

En actualizaciones sobre variables de instancia de objetos que no sean operaciones atómicas:

actualización de dos o más variablesactualización de variables long, double¿otros?

El costo de declarar métodos synchronized es:mayor lentitud de la ejecución de métodos (por la adquisición del mutex)peligro de deadlock: bloqueo mutuo de dos threads que esperan adquirir mutex intercambiados

09-03-2007Franco Guidi Polanco 53

Ejemplo de deadlockDadas las clases Batman y Robin:

public class Batman {Robin robin;public void setAsistente( Robin robin ){

this.robin = robin;}public synchronized void vuelveABaticueva(){

robin.subeAlBatimovil();}public synchronized usaBatiboomerang(){

// usa el batiboomerang como arma de defensa}

}public class Robin {

Batman batman;public void setJefe( Batman batman ){

this.batman = batman;}public synchronized powTonkPafBonk(){

batman.usaBatiboomerang();}

}

09-03-2007Franco Guidi Polanco 54

Ejemplo de deadlock (cont.)

… y una aplicación que instancia dichas clases y las usa como objetos compartidos por diferentes threads:

public class CiudadGotica {public static void main( String[] arg){Batman batman = new Batman();Robin robin = new Robin();batman.setAyudante( robin );robin.setjefe( batman );// aquí se inician distintas operaciones con threads,// entre ellos un thread Alfred y otro thread Pingüino}

}

NOTA:Ejemplo adaptado de “Programming Java Threads in the real world” (Parte 2), Allan Holub, disponible on-line: http://www.javaworld.com

09-03-2007Franco Guidi Polanco 55

Ejemplo de deadlock (cont.)

Imagine que en el ejemplo anterior ocurre lo siguiente:1.Un thread llamado Alfred invoca el método vuelveABaticueva()

sobre el objeto batman. Alfred obtiene el mutex de batman pero justo antes de que este método invoque el método subeAlBatimovil() de robin, su ejecución es interrumpida.

2.Otro thread, llamado Pingüino, invoca el método powTonkPafBonk()de robin. El thread Pingüino obtiene el mutex de robin, y trata de ejecutar la instrucción batman.usaBatiboomerang(). Para lograrlo debe adquirir también el mutex de batman, pero como éste está en poder del thread Alfred, se bloquea en espera de su liberación.

public synchronized void vuelveABaticueva(){robin.subeAlBatimovil();

}

public synchronized powTonkPafBonk(){batman.usaBatiboomerang();

}

(Clase Batman)

(Clase Robin)

09-03-2007Franco Guidi Polanco 56

Ejemplo de deadlock (cont.)

3. Entonces el thread Alfred es reactivado, y trata de invocar subeAlBatimovil() sobre robin. Para invocarlo, sin embargo, debe adquirir el mutex de robin que está en poder del thread Pingüino.

4. A este punto el thread Pingüino no puede reactivarse porque no puede obtener el mutex de batman (lo tiene el thread Alfred), ni tampoco el thread Alfred puede hacerlo, porque no puede obtener el mutex de robin (lo tiene el thread Pingüino)… ¡Deadlock!

public synchronized void vuelveABaticueva(){robin.subeAlBatimovil();

}(Clase Batman)

09-03-2007Franco Guidi Polanco 57

Re-adquisición del mutex

En Java un thread puede re-obtener el mutex de un objeto que éste ya tiene.

Esto evita que un thread incurra en deadlock por culpa de él mismo.

public class NoIncurroEnAutoDeadlock {public synchronized void a(){

b();}public synchronized void b(){

System.out.println( “Estoy en b”);}

}

09-03-2007Franco Guidi Polanco 58

Bloques synchronizedEs posible declarar como synchronized porciones de código dentro de un método de una clase.Esto permite implementar exclusión mutua sobre bloques de instrucciones.Formato:

Un thread, al ingresar a un bloque synchronized se bloquea en espera de la adquisición del mutex asociado al objeto (o arreglo) declarado en su encabezado. El mutexes liberado a la salida del bloque.

synchronized( objeto ){// instrucciones

}

09-03-2007Franco Guidi Polanco 59

Bloques synchronized: ejemplo

En el siguiente ejemplo, el método avanzar adquiere el mutex de motor para ejecutar algunas instrucciones:

public class Robot{Motor motor = new Motor();...public void avanzar(){

if( encendido() )synchronized( motor ){

avanzando = true;motor.aplicarPotencia( 10 );...

}else

preguntar( “Desea encender”, “Si”, “No”);...

}}

09-03-2007Franco Guidi Polanco 60

Bloques synchronized: ejemplo

Consecuencia: dado que en el bloque synchronized es adquirido el mutex de motor, ningún otro thread que requiera dicho mutex podrá ser ejecutado concurrentemente.En consecuencia se obtiene un acceso con exclusión mutua sobre la instancia de Motor, aun cuando esta clase no tenga ningún bloque ni método synchronized.

09-03-2007Franco Guidi Polanco 61

Bloques y métodos synchronizedEl objeto asociado al bloque synchronized puede ser la misma instancia sobre la cual se ejecuta el método (referencia this). Como consecuencia, las siguientes implementaciones son equivalentes:public class MiClase{

public synchronized void miMetodo(){// Instrucciones

}}

public class MiClase{public void miMetodo(){

synchronized( this ){// Instrucciones

}}

}

09-03-2007Franco Guidi Polanco 62

Bloques synchronized (cont.)Se pueden crear exclusiones en/entre métodos específicos:

public class Acta{double[] notasCatedra = new double[10];double[] notasAyudantia = new double[10];public void actualizarCatedra(){

synchronized( notasCatedra );// Instrucciones

}}public void imprimirCatedra(){

synchronized( notasCatedra );// Instrucciones

}}public void actualizarAyudantia(){

synchronized( notasAyudantia );// Instrucciones

}public void imprimirAyudantia(){

synchronized( notasAyudantia );// Instrucciones

}}

}

Excluyentes

Excluyentes

09-03-2007Franco Guidi Polanco 63

Sincronización de threads

El problema consiste en lograr que un thread actúe sólo cuando otro ha concluido cierta actividad (y viceversa): threads mutuamente excluyentes.Problema del productor/consumidor:

Un thread (Productor) genera un elemento que es agregado a un depósito, este elemento es consumido por otro thread (Consumidor)El depósito tiene capacidad limitada, cuando está lleno, el Productor debe esperar que se disponga de espacio nuevamente. Por su parte el consumidor debe esperar que haya elementos para poder retirarlos.

09-03-2007Franco Guidi Polanco 64

Supongamos que la capacidad del depósito es igual a 1:

El problema es más complejo que en los ejemplos anteriores: el depósito no sólo debe soportar acceso concurrente, sino que los threads deben también actuar sincronizadamente.

Sincronización de threads

Productor ConsumidorDepósito

09-03-2007Franco Guidi Polanco 65

Ejemplo de sincronización

Supongamos la siguiente implementación de un Productor y un Consumidor:

Ambos actúan sobre un objeto compartido de la clase Deposito.

public class Productor extends Thread {private Deposito deposito;public Productor(Deposito d) {

deposito = d;}public void run() {

for (int i=1;i<20 ;i++ )deposito.guardar();

}}

public class Consumidor extends Thread{private Deposito deposito;public Consumidor(Deposito d) {

deposito = d;}public void run() {

for (int i=1;i<20 ;i++ )deposito.sacar();

}}

09-03-2007Franco Guidi Polanco 66

Sincronización de threads: una solución simplista

Una solución simple sería que el productor verificara cada vez si hay espacio en el depósito, y si lo hay, entonces agregara un elemento a él.Lo mismo podría hacer el consumidor antes de intentar sacar un elemento del depósito.

public class Deposito{private int elementos = 0;public synchronized void guardar() {

if( elementos = 0 )elementos++;

return;}public synchronized void sacar() {

if( elementos > 0 )elementos--;

return;}

}

09-03-2007Franco Guidi Polanco 67

Sincronización de threads: una solución simplista (cont.)

Problema: alto consumo de recursos (CPU) en procesos improductivos.

Solución más adecuada: detener los threads hasta que se den las condiciones para que actúen.

CPU

09-03-2007Franco Guidi Polanco 68

Sincronización de threads: uso de métodos wait y notify

La clase Object provee el método wait() que detiene un thread hasta que le sea notificada la posibilidad de continuar.El método wait debe ser invocado sobre un objeto compartido por los threads a sincronizar (ej. el depósito)Para poder invocar el método wait es necesario que el thread tenga el mutex del objeto compartidoLa invocación de wait detiene el thread, lo pone en una lista de espera asociada al objeto, y libera su mutex.

Objeto

Lista de esperawait

09-03-2007Franco Guidi Polanco 69

Sincronización de threads: uso de métodos wait y notify (cont.)

El thread saldrá de la lista de espera cuando otro threadinvoque el método notify sobre el objeto compartido. Al salir de la lista de espera, se bloqueará en espera del mutex del objeto para continuar su ejecución.Una vez re-obtenido el mutex del objeto, el thread que salió de la lista de espera continuará la ejecución del método en la instrucción siguiente al llamado a wait.Si hay más de un thread en la lista de espera, notify re-activará sólo uno de ellos. El criterio de selección del thread a re-activar depende de la implementación de Java.Nota: El método wait puede generar una InterruptedException.

09-03-2007Franco Guidi Polanco 70

Sincronización de threads: uso de métodos wait y notify (cont.)

Notar que en este modelo la notificación es indirecta: el thread que invoca notify no tiene ninguna referencia al thread que está en espera. La notificación actúa sobre un objeto compartido, y al ser este notificado, un thread en espera es reactivado.

llegué

PedroInscripción paranotificación (wait)

Pedro

lleguéllegó Pedro

Listadeespera

09-03-2007Franco Guidi Polanco 71

Sincronización de threads: uso del método notifyAll

El método notifyAll permite reactivar todos los threads bloqueados en la lista de espera de un objeto, esto es, se vuelven todos ejecutables. Notar sin embargo que ellos podrán tomar el mutex sólo de uno a la vez.

Inscripción paranotificación (wait)

Pedro

lleguéllegué

Listadeespera

llegué

llegué

09-03-2007Franco Guidi Polanco 72

Ejemplo de sincronización (cont.)

El depósito está implementado de la siguiente forma:public class Deposito{

private int elementos = 0;public synchronized void guardar() {

try{if( elementos > 0) // Más adelante se verá que no está correcto

this.wait();} catch( InterruptedException e ){}elementos++; System.out.println( "Guardar - numero elementos: " + elementos );this.notify();

}public synchronized void sacar() {

try{if( elementos == 0) // Más adelante se verá que no está correcto

this.wait();} catch( InterruptedException e ){}elementos--;System.out.println( "Sacar - numero elementos: " + elementos );this.notify();

}}

09-03-2007Franco Guidi Polanco 73

Ejemplo de sincronización (cont.)

La aplicación crea dos threads (Productor y Consumidor), que accesan el objeto compartido (depósito):public class EjemploProductorConsumidor{

public static void main( String[] arg ) {Deposito deposito = new Deposito();Productor productor = new Productor( deposito );Consumidor consumidor = new Consumidor( deposito );productor.start();consumidor.start();

}}

09-03-2007Franco Guidi Polanco 74

Ejemplo de sincronización (cont.)

09-03-2007Franco Guidi Polanco 75

Ejemplo de sincronización (cont.)

Supongamos en un momento cualquiera que el depósitoestá vacío y se activa el thread Consumidor

ThreadProductor

ThreadConsumidor

depósito

MutexLista de espera

Adquiere el mutex de deposito

Se bloquea en espera del mutexde depósito

Adquiere el mutex de depósito

Verifica que depósito tenga elementos. Dado que no los tiene invoca wait del objeto depósito.La invocación de wait libera el mutex de depósito y el thread se agrega a la lista de espera (de depósito).

09-03-2007Franco Guidi Polanco 76

Ejemplo de sincronización (cont.)

ThreadProductor

ThreadConsumidor

depósito

MutexLista de espera

Invoca notify de depósito.

Verifica que depósito esté vacío y agrega un elemento.

Invoca notify de depósito (*)

Saca el elemento del depósito

Sale de la lista de espera y se bloquea en espera del mutex de depósito.Termina la ejecución del método y

libera el mutex.Adquiere el mutex de depósito.

Se bloquea en espera del mutexde depósito

(*) El efecto de notify en esta secuencia es nulo, pero ¿qué habría pasado si el mutex de depósito lo hubiera ganado el Productor?

09-03-2007Franco Guidi Polanco 77

Sincronización y bloqueo iterativo (spin lock)

En la implementación de Deposito existe aun un problema. Suponga que hay mas de un threadConsumidor, el depósito está vacío, y se da la siguiente secuencia de eventos:

1. Un thread Consumidor toma el mutex del depósito, y verifica la existencia de un elemento en él. Dado que el depósito está vacío, invoca el método wait (sobre el depósito), se bloquea en la lista de espera (de depósito), y libera su mutex.

2. El thread Productor es reactivado, adquiere el mutex de depósito, comprueba que éste está vacío, le agrega un elemento, e invoca notify (sobre el objeto depósito).

if( elementos == 0)this.wait();

09-03-2007Franco Guidi Polanco 78

Sincronización y bloqueo iterativo (spin lock)

3. El thread Consumidor que estaba en la lista de espera de depósito es notificado (sacado de dicha lista), y puesto en espera del mutex (de depósito).

4. El thread Productor libera el mutex de depósito.5. Otro thread Consumidor, que no estaba en la lista de

espera de depósito, adquiere su mutex, comprueba que el depósito tiene un elemento, lo saca del depósito, invoca notify (no interesa que ocurre con esto), y libera el mutex de depósito.

6. El primer thread Consumidor (que en el paso 3 había sido sacado de la lista de espera y bloqueado en espera del mutex) adquiere el mutex de depósito (antes de que el thread Productor trate de agregar un elemento), y continúa su ejecución en el punto en que estaba: trata de sacar un elemento, pero el depósito está vacío: ERROR.

09-03-2007Franco Guidi Polanco 79

Sincronización y bloqueo iterativo (spin lock)

Este problema nace del hecho que la especificación de Java no establece que la salida de la lista de espera y adquisición del mutex sean implementados como una operación atómica (distintas JVM se pueden comportar de distinto modo).Problemas análogos se presentan cuando:

Hay más de un Productor.La notificación ocurre con notifyAll en vez de notify.

09-03-2007Franco Guidi Polanco 80

Sincronización y bloqueo iterativo (spin lock)

Solución al problema anterior:reemplazar la estructura:

por:

if( condición de detención )wait();

while( condición de detención )wait(); Spin lock

09-03-2007Franco Guidi Polanco 81

Sincronización y bloqueo iterativo (spin lock)

Es decir, la clase Deposito queda:public class Deposito{

private int elementos = 0;public synchronized void guardar() {

try{while( elementos > 0)

this.wait();} catch( InterruptedException e ){}elementos++; System.out.println( "Guardar - numero elementos: " + elementos );this.notify();

}public synchronized void sacar() {

try{while( elementos == 0)

this.wait();} catch( InterruptedException e ){}elementos--;System.out.println( "Sacar - numero elementos: " + elementos );this.notify();

}}

09-03-2007Franco Guidi Polanco 82

Para saber más...

The Java Tutorial (Sun Microsystems)http://java.sun.com

The Java API (Sun Microsystems)http://java.sun.com

“Programming Java Threads in the Real World” (Parts 1-9) Allan Hollub.

http://www.javaworld.com