Java-Programacion Multithread - Escuela de Ingeniería Industrial

Transcripción

Java-Programacion Multithread - Escuela de Ingeniería Industrial
¿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.
Java: Programación Multithread
Franco Guidi Polanco
Un thread
Escuela de Ingeniería Industrial
Pontificia Universidad Católica de Valparaíso, Chile
[email protected]
Un programa
Franco Guidi Polanco
09-03-2007
¿Qué es un thread? (cont.)
2
Ejemplo
™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
Dos
threads
Un programa
™Una aplicación que posee un thread que saluda y
otro que se despide:
Un programa
for(int i = 1; i<100;i++)
for(int i = 1; i<100;i++)
System.out.printl(“Hola ” + i );
System.out.printl(“Chao ” + i );
Dos threads
Franco Guidi Polanco
09-03-2007
3
Franco Guidi Polanco
09-03-2007
4
Ejemplo (cont.)
Creación de múltiples threads en Java
™Los threads en Java se implementan por
medio de la clase java.lang.Thread.
™Output posible:
Hola
Hola
Chao
Hola
Chao
Chao
Hola
…
Franco Guidi Polanco
1
2
1
3
2
3
4
™Existen dos formas de crear threads en
Java:
ƒ Extendiendo la clase java.lang.Thread
ƒ Implementando la interfaz
java.lang.Runnable
09-03-2007
5
Creación de threads extendiendo la clase
java.lang.Thread
09-03-2007
09-03-2007
6
Creación de threads extendiendo la clase
java.lang.Thread: ejemplo 1
™El thread debe ser una clase que extiende la
clase Thread
™Se 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)
Franco Guidi Polanco
Franco Guidi Polanco
7
™Dos ejemplos de clases que extienden Thread:
public class Hola extends Thread {
public void run() {
for (int i=1;i<100;i++)
System.out.println( "Hola" + i );
}
}
public class Chao extends Thread {
public void run() {
for (int i=1;i<100;i++)
System.out.println( "Chao" + i );
}
}
Franco Guidi Polanco
09-03-2007
8
Creación de threads extendiendo la clase
java.lang.Thread: ejemplo 1 (cont.)
¿Qué ocurre en este ejemplo?
™La aplicación que lanza los threads:
...
public static void main(String arg[]) {
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” );
}
}
Hola h = new Hola();
Chao c = new Chao();
09-03-2007
9
c.start();
System.out.println( “Fin programa” );
Thread
Chao
c.start();
Thread
Hola
...
}
...
Franco Guidi Polanco
™Estos dos
programas
funcionan
de igual
modo
09-03-2007
10
11
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” );
}
}
NOTA:
Ejecución en JVM
sobre Windows XP.
El resultado puede ser
distinto en otras
plataformas
(se estudiará más
adelante)
09-03-2007
h.start();
Si las referencias a los threads no son
necesarias...
Ejecución del ejemplo 1
Franco Guidi Polanco
...
h.start();
™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).
Franco Guidi Polanco
Thread
main
Franco Guidi Polanco
09-03-2007
12
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.Runnable
™Particularmente se debe implementar en la clase
el método run() declarado en Runnable, con el
código que deberá ser ejecutado por el thread
™Para 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
Franco Guidi Polanco
09-03-2007
13
Creación de threads implementando la interfaz
java.lang.Runnable: ejemplo 2
™ Dos ejemplos de clases que implementan Runnable:
public class Hola implements Runnable {
public void run() {
for (int i=1;i<100;i++)
System.out.println( "Hola" + i );
}
}
public class Chao implements Runnable {
public void run() {
for (int i=1;i<100;i++)
System.out.println( "Chao" + i );
}
}
Franco Guidi Polanco
09-03-2007
15
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
}
...
}
™Instanciación y lanzamiento del thread:
...
Thread t = new Thread( new MiThread() );
t.start();
...
Franco Guidi Polanco
09-03-2007
14
Creación de threads implementando la interfaz
java.lang.Runnable: ejemplo 2 (cont.)
™ La aplicación que lanza los threads:
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” );
}
}
™ 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).
Franco Guidi Polanco
09-03-2007
16
(Nuevamente) si las referencias a los
threads no son necesarias...
™Estos dos
programas
funcionan
de igual
modo
Ejercicio
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” );
}
}
Franco Guidi Polanco
09-03-2007
17
™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.
Franco Guidi Polanco
Solución
09-03-2007
Solución (cont.)
™Versión que extiende Thread:
™Versión que implementa Runnable:
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 );
}
}
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 );
}
}
public class Ejercicio {
public static void main(String arg[]) {
Habla h = new Habla( “Hola” ) ;
Habla c = new Habla( “Chao” );
h.start();
c.start();
}
}
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();
}
}
Franco Guidi Polanco
09-03-2007
18
19
Franco Guidi Polanco
09-03-2007
20
Métodos útiles de la clase Thread: sleep
™Detiene la ejecución del thread actual por la
cantidad de milisegundos indicada como parámetro.
public static void sleep(long millis) throws InterruptedException
™Ejemplo:
Equivalente:
public class MyThread extends Thread {
...
public void run() {
...
try{
sleep( 1000 );
}catch(InterruptedException e){
//Interrupción
}
...
}
}
Franco Guidi Polanco
09-03-2007
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().
21
Métodos útiles de la clase Thread: sleep
Ejemplo
Métodos útiles de la clase Thread: sleep
Ejemplo
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){}
}
}
}
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){}
}
}
}
Franco Guidi Polanco
Detención de 1000 ms
(1 segundo)
Tiempo de detención
al azar
09-03-2007
22
Métodos útiles de la clase Thread: yield
™Detiene la ejecución del thread actual y
permite a otros threads ser ejecutados
public static void yield()
™Ejemplo:
Equivalente:
public class MyThread extends Thread {
...
public void run() {
...
yield();
...
}
}
Franco Guidi Polanco
09-03-2007
23
Franco Guidi Polanco
09-03-2007
Thread.yield();
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().
24
Métodos útiles de la clase Thread: yield
(Ejemplo)
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();
}
}
}
Franco Guidi Polanco
09-03-2007
25
Ciclo de vida de un thread
Ejecutándose
(Running)
yield
Nuevo thread
(New Thread)
start
Ejecutable
(Runnable)
Franco Guidi Polanco
09-03-2007
26
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:
sleep
wait
bloqueado en I/O
No ejecutable
(Not Runnable)
ƒ El thread invoca el método sleep
ƒ El thread invoca el método wait (*)
ƒ El thread se bloquea en una operación de I/O
finaliza el método run
Muerto
(Dead)
Franco Guidi Polanco
09-03-2007
Tomado de
“The Java Tutorial”
Sun Microsystems
27
(*) será estudiado más adelante
Franco Guidi Polanco
09-03-2007
28
Ciclo de vida de un thread (cont.)
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 transcurrido
ƒ si 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
Franco Guidi Polanco
09-03-2007
29
™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 detenido
ƒ Retorna false si el thread está en el estado “Nuevo
thread” (no ha sido lanzado) o está “muerto” (método
run terminado)
Franco Guidi Polanco
Ejecución de threads
ƒ En un sistema con múltiples CPU, cada CPU podría
ejecutar un thread distinto
™Concurrencia
ƒ Si no es posible el paralelismo, una CPU es responsable
de ejecutar múltiples threads
Thread A
Thread B
Thread A
Thread A
Thread B
Thread B
Thread A
Thread B
Thread A
...
...
...
CPU
CPU
CPU
09-03-2007
30
Prioridades de los threads
™Paralelismo
Franco Guidi Polanco
09-03-2007
31
™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
Franco Guidi Polanco
09-03-2007
32
Prioridades de los threads (cont.)
El “fixed priority scheduling” de Java
™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
ƒ public static final int MIN_PRIORITY
ƒ public static final int NORM_PRIORITY
Franco Guidi Polanco
10
1
5
09-03-2007
33
Threads y la portabilidad de Java:
debilidades
09-03-2007
34
™La cosa puede ser peor aun:
ƒ ... 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-2007
Franco Guidi Polanco
Threads y la portabilidad de Java:
debilidades (cont.)
™La responsabilidad de ejecución de los
threads es pasada al sistema operativo
™Pero...
Franco Guidi Polanco
™Entre todos los threads en estado “ejecutable” es
escogido el thread con la prioridad más alta
™Si hay dos threads con la misma prioridad, es
escogido uno de ellos en modo round-robin
™Cuando 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”.
35
ƒ 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)
Franco Guidi Polanco
09-03-2007
36
Threads egoístas
Threads egoístas (cont.)
™ 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:
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 );
}
}
™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();
}
}
ƒ En un sistema sin time slicing, este se vuelve un thread egoísta.
Franco Guidi Polanco
09-03-2007
37
Franco Guidi Polanco
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();
}
}
}
Franco Guidi Polanco
09-03-2007
09-03-2007
38
Acceso a datos compartidos
™Es común que dos o más threads tengan acceso a
objetos en común
™Ejemplo
ƒ 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++;
}
}
39
Franco Guidi Polanco
09-03-2007
40
Acceso a datos compartidos (cont.)
Acceso a datos compartidos (cont.)
™Se espera que ocurra lo siguiente:
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();
}
}
Thread Hola
mensaje[pos]=msg;
pos++;
mensaje[pos]=msg;
pos++;
41
Acceso a datos compartidos (cont.)
Thread Chao
mensaje[0]=“Hola”
mensaje[pos]=msg;
pos++;
pos++;
pos = 2
mensaje[pos]=msg;
pos++;
mensaje[2]=“Hola”
pos = 3
mensaje[pos]=msg;
pos++;
Franco Guidi Polanco
mensaje[3]=“Chao”
pos = 4
09-03-2007
42
Bloqueo del objetos compartidos
™ Pero podría ocurrir lo siguiente:
mensaje[pos]=msg;
Franco Guidi Polanco
mensaje[1]=“Chao”
pos = 2
mensaje[2]=“Hola”
pos = 3
mensaje[pos]=msg;
pos++;
09-03-2007
Thread Hola
mensaje[0]=“Hola”
pos = 1
mensaje[pos]=msg;
pos++;
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();
}
}
Franco Guidi Polanco
Thread Chao
mensaje[0]=“Chao”
pos = 1
mensaje[pos]=msg;
mensaje[3]=“Chao”
pos++;
pos = 4
mensaje[3]=“Hola”
™ 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.
pos = 5
09-03-2007
43
Franco Guidi Polanco
09-03-2007
44
Bloqueo del objetos compartidos: métodos
synchronized
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.
™ En Java el bloqueo de un objeto ocurre cuando un thread
entra a un método declarado como synchronized (de un
objeto compartido).
public class Historial {
™ Ejemplo:
...
public synchronized void agregar(String msg) {
mensaje[pos] = msg;
pos++;
}
}
Objeto
™ Al momento de entrar a un método synchronized de un
objeto compartido, un thread se encontrará con una de las
siguientes situaciones:
Mutex
ƒ 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.
Threads
Franco Guidi Polanco
09-03-2007
45
Bloqueo del objetos compartidos: métodos
synchronized (cont.)
Franco Guidi Polanco
09-03-2007
46
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
™En el caso de nuestro ejemplo, mientras un thread
se encuentra ejecutando el método agregar,
ningún otro thread puede ejecutar dicho método
(pues la ejecución de agregar requiere el mutex
del objeto historial).
Thread
Hola
mutex
historial.agregar()
Thread
Chao
historial.agregar()
historial
mensaje[pos]=msg;
pos++;
Franco Guidi Polanco
09-03-2007
47
Franco Guidi Polanco
09-03-2007
48
Bloqueo del objetos compartidos: métodos
synchronized (cont.)
Métodos synchronized: ejemplo
™La invocación a sleep dentro de un thread no
libera el mutex de los objetos que eventualmente
pudiera tener en su poder.
Zzz
™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;
}
z
Objeto
}
Franco Guidi Polanco
09-03-2007
49
™En 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-2007
09-03-2007
50
¿Cuándo deben declararse métodos
synchronized?
Métodos synchronized
Franco Guidi Polanco
Franco Guidi Polanco
™En actualizaciones sobre variables de instancia de
objetos que no sean operaciones atómicas:
ƒ actualización de dos o más variables
ƒ actualizació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
51
Franco Guidi Polanco
09-03-2007
52
Ejemplo de deadlock
Ejemplo de deadlock (cont.)
™ Dadas las clases Batman y Robin:
™ … y una aplicación que instancia dichas clases y las usa
como objetos compartidos por diferentes threads:
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 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
public class Robin {
Batman batman;
public void setJefe( Batman batman ){
this.batman = batman;
}
public synchronized powTonkPafBonk(){
batman.usaBatiboomerang();
}
}
Franco Guidi Polanco
09-03-2007
}
}
NOTA:
Ejemplo adaptado de “Programming Java Threads in the real world” (Parte 2),
Allan Holub, disponible on-line: http://www.javaworld.com
53
Franco Guidi Polanco
Ejemplo de deadlock (cont.)
3.
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.
Franco Guidi Polanco
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.
(Clase Batman)
public synchronized void vuelveABaticueva(){
robin.subeAlBatimovil();
}
4.
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.
(Clase Robin)
54
Ejemplo de deadlock (cont.)
™Imagine que en el ejemplo anterior ocurre lo siguiente:
(Clase Batman)
09-03-2007
public synchronized void vuelveABaticueva(){
robin.subeAlBatimovil();
}
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 powTonkPafBonk(){
batman.usaBatiboomerang();
}
09-03-2007
55
Franco Guidi Polanco
09-03-2007
56
Bloques synchronized
Re-adquisición del mutex
™En Java un thread puede re-obtener el mutex de
un objeto que éste ya tiene.
public class NoIncurroEnAutoDeadlock {
public synchronized void a(){
b();
}
public synchronized void b(){
System.out.println( “Estoy en b”);
}
}
synchronized( objeto ){
// instrucciones
}
™Esto evita que un thread incurra en deadlock por
culpa de él mismo.
Franco Guidi Polanco
09-03-2007
57
Bloques synchronized: ejemplo
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-2007
™ 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 mutex
es liberado a la salida del bloque.
Franco Guidi Polanco
09-03-2007
58
Bloques synchronized: ejemplo
™En el siguiente ejemplo, el método avanzar
adquiere el mutex de motor para ejecutar algunas
instrucciones:
Franco Guidi Polanco
™ Es 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:
59
™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.
Franco Guidi Polanco
09-03-2007
60
Bloques y métodos synchronized
Bloques synchronized (cont.)
™ El 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
}
}
}
Franco Guidi Polanco
09-03-2007
61
™ 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
}
}
}
Franco Guidi Polanco
Sincronización de threads
ƒ 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-2007
Excluyentes
09-03-2007
62
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:
Franco Guidi Polanco
Excluyentes
63
™Supongamos que la capacidad del depósito es
igual a 1:
Productor
Depósito
Consumidor
™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.
Franco Guidi Polanco
09-03-2007
64
Sincronización de threads: una solución
simplista
Ejemplo de sincronización
™Supongamos la siguiente implementación de un
Productor y un Consumidor:
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();
}
}
™Ambos actúan sobre un objeto compartido de la
clase Deposito.
Franco Guidi Polanco
09-03-2007
65
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.
™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;
}
}
Franco Guidi Polanco
09-03-2007
66
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 compartido
™ La invocación de wait detiene el thread, lo pone en una lista
de espera asociada al objeto, y libera su mutex.
Objeto
Lista de espera
CPU
Franco Guidi Polanco
09-03-2007
wait
67
Franco Guidi Polanco
09-03-2007
68
Sincronización de threads: uso de métodos
wait y notify (cont.)
™ El thread saldrá de la lista de espera cuando otro thread
invoque 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 reactivará 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.
Franco Guidi Polanco
09-03-2007
69
Sincronización de threads: uso del método
notifyAll
Lista
de
espera
llegué
llegué
Pedro
llegué
Inscripción para
notificación (wait)
Franco Guidi Polanco
09-03-2007
™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
Lista
reactivado.
de
espera
llegué
llegué
llegó Pedro
Pedro
Pedro
Inscripción para
notificación (wait)
Franco Guidi Polanco
09-03-2007
70
Ejemplo de sincronización (cont.)
™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.
llegué
Sincronización de threads: uso de métodos
wait y notify (cont.)
71
™ 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();
}
}
Franco Guidi Polanco
09-03-2007
72
Ejemplo de sincronización (cont.)
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();
}
}
Franco Guidi Polanco
09-03-2007
73
Ejemplo de sincronización (cont.)
Se bloquea en espera del mutex
de depósito
Mutex
Lista de
espera
depósito
Thread
Consumidor
Thread
Productor
Verifica que depósito esté
vacío y agrega un elemento.
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-2007
74
75
Mutex
Lista
de espera
Thread
Consumidor
depósito
Invoca notify de depósito.
Termina la ejecución del método y
libera el mutex.
Se bloquea en espera del mutex
de depósito
Sale de la lista de espera y se bloquea en
espera del mutex de depósito.
Adquiere el mutex de depósito.
Saca el elemento del depósito
…
Invoca notify de depósito (*)
Adquiere el mutex de deposito
Franco Guidi Polanco
09-03-2007
Ejemplo de sincronización (cont.)
™ Supongamos en un momento cualquiera que el depósito
está vacío y se activa el thread Consumidor
Thread
Productor
Franco Guidi Polanco
Franco Guidi Polanco
(*) 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-2007
…
76
Sincronización y bloqueo iterativo (spin lock)
™
En la implementación de Deposito existe aun un
problema. Suponga que hay mas de un thread
Consumidor, 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.
if( elementos == 0)
this.wait();
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).
Franco Guidi Polanco
09-03-2007
77
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.
Franco Guidi Polanco
09-03-2007
79
Sincronización y bloqueo iterativo (spin lock)
3.
4.
5.
6.
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).
El thread Productor libera el mutex de depósito.
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.
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.
Franco Guidi Polanco
09-03-2007
78
Sincronización y bloqueo iterativo (spin lock)
™Solución al problema anterior:
ƒ reemplazar la estructura:
if( condición de detención )
wait();
ƒ por:
while( condición de detención )
wait();
Franco Guidi Polanco
09-03-2007
Spin lock
80
Para saber más...
Sincronización y bloqueo iterativo (spin lock)
™The Java Tutorial (Sun Microsystems)
™ Es decir, la clase Deposito queda:
ƒ http://java.sun.com
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();
}
}
Franco Guidi Polanco
09-03-2007
™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
81
Franco Guidi Polanco
09-03-2007
82

Documentos relacionados