Son una implementación del concepto de región crítica. Un bloque

Anuncio
TEMA 5: Control de la Concurrencia en Java
(API Estándar)
CONTENIDO
Exclusión Mutua con código synchronized.
Exclusión Mutua con métodos synchronized.
Protocolos de Control de la Exclusión Mutua.
Interbloqueos
Sincronización: wait(), notify(),notifyAll()
Condiciones de Guarda
Protocolos de Sincronización
Diseño de Monitores en Java
BIBLIOGRAFÍA RECOMENDADA:
[Göe06] Göetz et al. Java Concurrency in Practice, 2006.
[Oaks04] Oaks & Wong. Java Threads. O`Reilly, 2004.
[Well04] Wellings, A, Concurrent and Real Time
Programming in Java. John Wiley&Sons ,2004
Sintaxis para Bloques de Código Sincronizado
public metodo_x (parametros){
//resto codigo del metodo; no se ejecuta en
em.
/*comienzo sección crítica*/
synchronized (object){
Bloque de sentencias críticas
//Todo el código situado entre
llaves se
ejecuta bajo e.m.
}
/*fin sección crítica*/
}//metodo_x
© Antonio Tomeu
1
EXCLUSIÓN MUTUA ENTRE HILOS
§
En Java la exclusión mutua se logra a nivel de
objetos. Todo objeto tiene asociado un cerrojo (lock).
§
Técnicas de Control de la E.M. en Java.
Semántica de Bloques de Código Sincronizado
n
Bloques sincronizados. El segmento de código que deba
ejecutarse en e.m. se etiqueta como synchronized. Es el
equivalente a una región crítica en java.
§ Métodos sincronizados. Aquellos instancias de recursos
que deban manejarse bajo e.m. se encapsulan en una clase
y todos sus métodos modificadores son etiquetados con la
palabra reservada synchronized.
§
§
Por tanto, dos o más hilos están en e.m. cuando
llaman a métodos synchronized de un objeto (de
exclusión mutua) o ejecutan métodos que tienen
bloques de código sincronizados
© Antonio Tomeu
Control de la Concurrencia en Java:
2 API Estándar
HILOS EN ESPERA
n
n
n
Son una implementación del concepto de
región crítica.
Un bloque de código está sincronizado
respecto de un objeto (puede ser el mismo
objeto, this)
El bloque sólo se ejecuta si se obtiene el
cerrojo sobre el objeto.
Útil para adecuar código secuencial a un
entorno concurrente
© Antonio Tomeu
Control de la Concurrencia en Java:
5 API Estándar
Política de Acceso de los Hilos en Espera
HILOS EN ESPERA
(BLOQUEADOS)
Control de la Concurrencia en Java:
4 API Estándar
(BLOQUEADOS)
HILO CON BLOQUEO
ADQUIRIDO
OBJETO DE
EX. MUTUA
HILO CON BLOQUEO
ADQUIRIDO
CERROJO
OBJETO DE
EX. MUTUA
HILO LIBERANDO
BLOQUEO
ESPERA POR CERROJO
Datos en em.
Código
Sincronizado
CUESTIONES A DETERMINAR:
• ¿Cómo establecer el bloqueo a nivel sintáctico?
• ¿Cuál es la semántica del bloqueo?
• ¿Qué política de acceso se da a los hilos en espera?
© Antonio Tomeu
Control de la Concurrencia en Java:
3 API Estándar
© Antonio Tomeu
Control de la Concurrencia en Java:
6 API Estándar
public class Ejemplo_Bloques_Sincronizados {
Output sin/con synchronized
private int Valor_1;
private int Valor_2;
protected Object Cerrojo_1 = new Object ();
protected Object Cerrojo_2 = new Object ();
public int Observa_1 () {
synchronized (Cerrojo_1){return (Valor_1);
}
}
public void Modifica_1 (int dato) {synchronized (Cerrojo_1){
Valor_1 = dato;
}
}
public void Modifica_2 (int dato) { synchronized (Cerrojo_2){
Valor_2 = dato;
}
}
public int Observa_2 () { synchronized (Cerrojo_2){
return (Valor_2);
}
}
public Ejemplo_Bloques_Sincronizados() {
synchronized (Cerrojo_1){
synchronized (Cerrojo_2){
Valor_1 = Valor_2 = 1;
}
}
}
}
© Antonio Tomeu
Control de la Concurrencia en Java:
7 API Estándar
© Antonio Tomeu
Pasando el cerrojo como parámetro
Acotando la Sección Crítica…
public class codBloqueo //usa el cerrojo del propio objeto
{
private int numVueltas;
public codBloqueo(int vueltas){numVueltas = vueltas;}
public void metodo()
{
synchronized(this) //bloqueo para acceso a seccion critica
{
for(int i=1; i<=numVueltas; i++) System.out.print(i+" ");
}
}
}
© Antonio Tomeu
Control de la Concurrencia en Java:
8 API Estándar
public class MuestraBloqueo
implements Runnable
{
private Object o;
public MuestraBloqueo(Object p) //cerrojo como parámetro del constructor
{o = p;}
public void run()
{
synchronized(o)//primer hilo que llega adquiere cerrojo
{
for(int i=1;i<100;i++){ //no habra entrelazado de impresiones
System.out.println("Iteracion "+i+" del hilo "+this.toString());
for(int j=1;j<100;j++); //retraso para visualizar
}
}
}
public static void main(String[] args)
{
Object lock = new Object(); //objeto para cerrojo compartido por los hilos
Thread h1 = new Thread(new MuestraBloqueo(lock), "hilo 1");
Thread h2 = new Thread(new MuestraBloqueo(lock), "hilo 2");
h1.setPriority(Thread.MIN_PRIORITY);
h2.setPriority(Thread.MAX_PRIORITY);
h1.start();
h2.start();
}
}
© Antonio Tomeu
public UsacodBloqueo(codBloqueo l) {cerrojo = l;}
public void run()
{
cerrojo.metodo(); //llamada a metodo que tiene codigo sincronizado
}
public static void main(String[] args)
{
codBloqueo aux = new codBloqueo(200);
UsacodBloqueo h1 = new UsacodBloqueo(aux);
UsacodBloqueo h2 = new UsacodBloqueo(aux);
h2.start();
h1.start();
}
Control de la Concurrencia en Java:
11 API Estándar
EJERCICIOS
… y usándola desde hilos en modo seguro
public class UsacodBloqueo
extends Thread
{
codBloqueo cerrojo; //referencia a objeto compartido
Control de la Concurrencia en Java:
10 API Estándar
n
n
n
Descargue otra vez nuestra vieja clase
Cuenta_Banca.java
Decida que código debe ser sincronizado,
y sincronícelo. La nueva clase será
Cuenta_Banca_Sync.ja va
Escriba un programa multihebrado que
use objetos de la clase anterior.Llámelo
UsaCuenta_Banca_Sync .java
}
© Antonio Tomeu
Control de la Concurrencia en Java:
9 API Estándar
© Antonio Tomeu
Control de la Concurrencia en Java:
12 API Estándar
Sintaxis para Métodos Sincronizados
Control con Métodos Sincronizados
public class MuestraBloqueoObjeto {
public synchronized void metodoA()
{
for(int i=1;i<100;i++){
System.out.println("Iteracion "+i+" del metodo A ");
for(int j=1;j<100;j++);//retraso para visualizar
}
System.out.println("metodo A liberando cerrojo...");
}
public synchronized metodo_x (parametros)
{
//Bloque de sentencias del método
//Todo el código del método se ejecuta en
em.
}//metodo_x
public synchronized void metodoB()
{
for(int i=1;i<100;i++){
System.out.println("Iteracion "+i+" del metodo B ");
for(int j=1;j<100;j++);
}
System.out.println("metodo B liberando cerrojo...");
}
}
© Antonio Tomeu
Control de la Concurrencia en Java:
13 API Estándar
Semántica para Métodos Sincronizados
n
n
n
n
Un método synchronized fuerza la e.m entre
todos los hilos que lo invocan
También fuerza la e.m. con otros métodos
synchronized del objeto
Cualquier hilo que trate de ejecutar un
método sincronizado deberá esperar si
otro método sincronizado ya está en
ejecución
Los métodos no sincronizados pueden
seguir ejecutándose concurrentemente
© Antonio Tomeu
Control de la Concurrencia en Java:
16 API Estándar
public class UsaMuestraBloqueoObjeto
implements Runnable
{
private MuestraBloqueoObjeto p; //referencia objeto compartido
private int caso;
public UsaMuestraBloqueoObjeto(MuestraBloqueoObjeto o, int val)
{p=o; caso=val;}
public void run()
{
switch(caso){
case 0: p.metodoA(); break;
case 1: p.metodoB(); break;
}
}
public static void main(String[] args)
{
MuestraBloqueoObjeto monitor = new MuestraBloqueoObjeto();
Thread h1 = new Thread(new UsaMuestraBloqueoObjeto(monitor,0));
Thread h2 = new Thread(new UsaMuestraBloqueoObjeto(monitor,1));
h1.start();
h2.start();
}
}
© Antonio Tomeu
Control de la Concurrencia en Java:
14 API Estándar
Además…
El cerrojo está asociado a cada
instancia de una clase (objeto)
n Los métodos de clase (static)
también pueden ser synchronized.
n En clases heredadas, los métodos
sobreescritos pueden ser
sincronizados o no, sin afectar a
cómo era (y es) el método de la
superclase.
n
© Antonio Tomeu
Control de la Concurrencia en Java:
17 API Estándar
/*Ejemplo de Exclusion Mutua entre Hilos
*@author Antonio J. Tomeu
*Varios hilos concurrentes modifican contador protegido bajo e.m.
*El metodo que incrementa al contador es synchronized
*Crea un array de hilos que incrementan bajo e.m. el contador
*Sintaxis de uso: java ExMutua n m donde:
*n es el numero de hilos concurrentes
*m es el valor inicial del contador
*/
class ObCritico
{
private int Dato; //Contiene el objeto critico
public ObCritico (int VInicial) //el constructor
{Dato = VInicial;}
public synchronized void Incremento() //e ejecutar bajo e.m.
{Dato++;}
public int Valor () //hace una lectura. No necesita e.m.
{return (Dato);}
}
© Antonio Tomeu
Control de la Concurrencia en Java:
15 API Estándar
© Antonio Tomeu
Control de la Concurrencia en Java:
18 API Estándar
public class ExMutua extends Thread
{
private ObCritico SC;
public ExMutua (ObCritico SecCritica)
{SC = SecCritica;}
public void run()
{
for(;;)
{
SC.Incremento();
System.out.println("El hilo con id. "+getName()+" ha
ajustado el objeto a valor "+SC.Valor());
}
}
public static void main(String[] args)
{
if (args.length !=2)
{ System.err.println ("Sintaxis: java ExMutua n m");
System.exit (1);
}
int NumHilos = Integer.valueOf(args[0]).intValue(); //num. de hilos
ObCritico ContadorCritico = new ObCritico
(Integer.valueOf(args[1]).intValue());
© Antonio Tomeu
Control de la Concurrencia en Java:
19 API Estándar
Protocolo 1 de Control de E.M. en Java
n
n
n
Acceder al recurso compartido donde sea
necesario
Definir un objeto para control de la
exclusión mutua (o usar el propio objeto,
this).
Sincronizar el código crítico utilizando el
cerrojo del objeto de control dentro de un
bloque synchronized de instrucciones
© Antonio Tomeu
Control de la Concurrencia en Java:
22 API Estándar
Protocolo 2 de Control de E.M. en Java
ExMutua [] Hilos = new ExMutua [NumHilos];
for(int i=0; i<=NumHilos-1; i++)
{Hilos[i] = new ExMutua (ContadorCritico);}
for(int i=0; i<=NumHilos-1; i++)
{Hilos[i].start();}
}
}
n
n
n
n
n
© Antonio Tomeu
Control de la Concurrencia en Java:
20 API Estándar
Encapsular el recurso crítico en una clase.
Definir todos los métodos de la clase como
no estáticos y synchronized
O al menos, todos los modificadores
Crear hilos que compartan una instancia
de la clase creada
!OJO¡ Esto no provee sincronización
© Antonio Tomeu
EJERCICIOS
n
n
n
Descargue otra vez nuestra vieja clase
Cuenta_Banca.java
Decida que métodos deben ser
sincronizados, y sincronícelos. La nueva
clase será Cuenta_Banca_Sync_Met.java
Escriba un programa multihebrado que
use objetos de la clase anterior.Llámelo
Usa_Cuenta_Banca_Sync_Met.java
© Antonio Tomeu
Control de la Concurrencia en Java:
21 API Estándar
Control de la Concurrencia en Java:
23 API Estándar
EJERCICIOS
n
n
n
Modele con una clase alguna situación donde
haya presencia de concurrencia y condiciones de
concurso. Guárdela en Recurso.java
Modifique la clase anterior protegiendo al recurso
mediante el Protocolo 2 de Control de la E.M.
Llame a la clase Recurso_em.java.
Escriba ahora un programa llamado
Prueba_Recurso_em.java que haga uso de la
clase anterior. Debe crear varios hilos
concurrentes mediante implementación de la
interfaz Runnable.
© Antonio Tomeu
Control de la Concurrencia en Java:
24 API Estándar
SINCRONIZACIÓN ENTRE HILOS
La Posesión del Bloqueo es por Hilo…
n
n
n
n
n
Un método sincronizado puede invocar a
otro método sincronizado sobre el mismo
objeto (REENTRANCIA)
Permite llamadas recursivas a métodos
sincronizados
Permite invocar método herededados
sincronizados
Descargue obj_protected.java y
usa_bj_protected.java. Pruébelos: ¿a
qué conclusión llega?
Incorpore ahora un método m3() recursivo
dentro de obj_protected. java . ¿Se
mantiene su conclusión?
© Antonio Tomeu
§
En Java la sincronización entre hilos se logra con los métodos wait,
notify y notifyAll (Clase Object)
§
Deben ser utilizados únicamente dentro de métodos o código de tipo
synchronized.
§
Cuando un hilo llama a un método que hace wait las siguientes
acciones son ejecutadas atómicamente:
1. Hilo llamante suspendido y bloqueado.
2. Exclusión mutua sobre el objeto liberada.
3. Hilo colocado en una cola única (el wait-set) de espera asociada
al objeto.
§
Cuando un hilo llama a un método que hace notify, uno de los
hilos bloqueados en la cola pasa a listo. Java no especifica cuál.
Depende de la implementación (JVM)Si se llama al método
notifyAll todos los hilos de dicha cola son desbloqueados y pasan
a listos. Accederá al objeto aquél que sea planificado.
Si el wait-set está vacío, notify y notifyAll no tienen efecto.
§
Control de la Concurrencia en Java:
25 API Estándar
© Antonio Tomeu
Control de la Concurrencia en Java:
28 API Estándar
INTERBLOQUEO (DEADLOCK) ENTRE HILOS (1/2)
Se producen cuando hay condiciones
de espera de liberación de bloqueos
cruzadas entre dos hilos
n Sólo pueden evitarse mediante un
análisis cuidadoso y riguroso del uso
de código sincronizado
n
© Antonio Tomeu
Control de la Concurrencia en Java:
26 API Estándar
INTERBLOQUEO (DEADLOCK) ENTRE HILOS (2/2)
PATRÓN ESTÁNDAR
public class Deadlock {
public static void main(String[] args)
{
final Object region_A = new Object();
final Object region_B = new Object();
Thread Hilo_A = new Thread(new Runnable(){
public void run()
{
synchronized(region_A)
{
synchronized(region_B)
{
calculo();
}
}
}
});
Thread Hilo_B = new Thread(new Runnable(){
public void run()
{
synchronized(region_B)
{
synchronized(region_A)
{
calculo();
}
}
}
});
public class aDormir extends Thread
{
public aDormir() {}
public void run()
{
System.out.println("El hilo "+this.getName()+" dijo: mi vida activa
fue breve...");
synchronized(this)
{
try{wait();}catch (InterruptedException e){} //cada hilo dormido sobre
su propio cerrojo
System.out.println(this.getName()+" dijo: pero he revivido...");
}
}
public void despertar(){synchronized (this){notify();}}
public static void main(String[] args)
{
aDormir [] h = new aDormir[10];
for(int i=0; i<10;i++)
{h[i]=new aDormir(); h[i].start();}
h[5].despertarUno();
Hilo_B.start();
Hilo_A.start();
}
}
}
© Antonio Tomeu
Control de la Concurrencia en Java:
27 API Estándar
}
30
Protocolo de Sicronización Inter-Hilos en Java
Con Espera Ocupada
public class aDormir2 extends Thread
{
Object lock;
public aDormir2(Object l) {lock=l;}
public void run()
{
System.out.println("El hilo "+this.getName()+" dijo: mi vida activa fue
breve...");
synchronized(lock)
{
try{lock.wait();}catch (InterruptedException e){} //hilos dormidos sobre
un cerrojo común
System.out.println(this.getName()+" dijo: pero he revivido...");
}
}
// Thread A
public void waitForMessage() {
while (hasMessage == false) {
Thread.sleep(100);
}
}
PRUEBE AHORA aDormir3.java
public void despertar(){synchronized (lock){lock.notify();}}
public void despertarTodos(){synchronized (lock){lock.notifyAll();}}
// Thread B
public void setMessage(String message) {
...
hasMessage = true;
public static void main(String[] args)
{
Object cerrojo = new Object();
aDormir2 [] h = new aDormir2[10];
for(int i=0; i<10;i++)
{h[i]=new aDormir2(cerrojo); h[i].start();}
h[5].despertar();
h[5].despertarTodos();
System.out.print("Todos terminaron..."); //¿observa algo anómalo?
© Antonio Tomeu
31
Control de la Concurrencia en Java:
34 API Estándar
}}
Protocolo de Sicronización Inter-Hilos en Java
con sincronización wait-notify
Condiciones de Guarda
§
No se puede escribir código de hilos asumiendo
que un hilo concreto recibirá la notificación.
§
Diferentes hilos pueden estar bloqueados sobre
un mismo objeto a la espera de diferentes
condiciones
§
Dado que notifyAll() los despierta a todos de
forma incondicional, es posible que reactive hilo
para los cuales no se cumple aún la condición de
espera.
Solución: siempre que el hilo usuario invoca a
wait(), lo primero al despertar es volver a
comprobar su condición particular, volviendo al
bloqueo si ésta aún no se cumple.
§
© Antonio Tomeu
// Thread B
public synchronized void setMessage(String message) {
...
notify();
OJO: No es perfecto. La señal puede perderse (y se pierde) si nadie
está esperándola.
SOLUCIÓN: Utilizar condiciones. A sólo se bloqueará si al comprobar
una condición ésta es falsa, y B se asegurará de hacer que la
condición sea verdadera antes de notificar.
Control de la Concurrencia en Java:
32 API Estándar
Patrón de Código para Condiciones de Guarda
public synchronized void m_receptor_señal(){
//hilo usuario se bloqueará en el metodo a espera de
condición
//el hilo usuario pasa al wait-set
while (!condicion) try{wait();} //condición de guarda
catch ()(Exception e){}
//codigo accedido en e.m.
}
-----------------------------------------------------------public synchronized void m_emisor_señal(){
//hilo usuario enviará una señal a todos los hilos del waitset
//la guarda garantiza que se despiertan los adecuados
//codigo en e.m. que cambia las condiciones de estado
notiyAll();
}
© Antonio Tomeu
// Thread A
public synchronized void waitForMessage() {
try {
wait();
} catch (InterruptedException ex) { }
}
Control de la Concurrencia en Java:
33 API Estándar
© Antonio Tomeu
class Sincro
{
Control de la Concurrencia en Java:
35 API Estándar
Patrón “sincronizador”
int Turno;
public Sincro(int t)
{Turno = t;}
public synchronized void metodo1()
{
while(Turno != 1)
try {wait();} catch (Exception e){}
System.out.println(" Turno al hilo1...");
Turno=2;
notifyAll();
}
public synchronized void metodo2()
{
while(Turno != 2)
try {wait();} catch (Exception e){}
System.out.println("Turno al hilo 2...");
Turno = 3;
notifyAll();
}
© Antonio Tomeu
Control de la Concurrencia en Java:
36 API Estándar
public synchronized void metodo3()
{
while(Turno != 3)
try {wait();} catch (Exception e){}
System.out.println("Turno al hilo 3...");
Turno = 1;
notifyAll();
}
}//Sincro
class Hilo1
{
Sincro
public
public
{
extends Thread
ref;
Hilo1(Sincro obj){ref=obj;}
void run()
for(;;){ref.metodo1();}
}
}//Hilo1
© Antonio Tomeu
Control de la Concurrencia en Java:
37 API Estándar
class Hilo2 extends Thread
{
Sincro ref;
public Hilo2(Sincro obj){ref=obj;}
public void run()
{
for(;;){ref.metodo2();}
}
}//Hilo2
class Hilo3
{
Sincro
public
public
{
extends Thread
ref;
Hilo3(Sincro obj){ref=obj;}
void run()
for(;;){ref.metodo3();}
}
}//Hilo3
© Antonio Tomeu
Control de la Concurrencia en Java:
38 API Estándar
public class Sincronizacion
{
public static void main (String [] args)
{
Sincro m = new Sincro(2);
new Hilo1(m).start();
new Hilo2(m).start();
new Hilo3(m).start();
System.out.println("hilos lanzados...");
}//main
}//Sincronizacion
© Antonio Tomeu
Control de la Concurrencia en Java:
39 API Estándar
EJERCICIO
Utilizando condiciones de guarda,
modele un protocolo de
sincronización simple Hilo B -> Hilo A
que sea completamente correcto.
n Guarde su código en ficheros
S1.java, S2.java…
n
© Antonio Tomeu
Control de la Concurrencia en Java:
40 API Estándar
Documentos relacionados
Descargar